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
20 changes: 13 additions & 7 deletions src/browser/dom/event_target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ const Page = @import("../page.zig").Page;
const EventHandler = @import("../events/event.zig").EventHandler;

const DOMException = @import("exceptions.zig").DOMException;
const Nod = @import("node.zig");
const nod = @import("node.zig");

// EventTarget interfaces
pub const Union = Nod.Union;
pub const Union = union(enum) {
node: nod.Union,
xhr: *@import("../xhr/xhr.zig").XMLHttpRequest,
};

// EventTarget implementation
pub const EventTarget = struct {
Expand All @@ -39,18 +41,22 @@ pub const EventTarget = struct {
// The window is a common non-node target, but it's easy to handle as
// its a singleton.
if (@intFromPtr(et) == @intFromPtr(&page.window.base)) {
return .{ .Window = &page.window };
return .{ .node = .{ .Window = &page.window } };
}

// AbortSignal is another non-node target. It has a distinct usage though
// so we hijack the event internal type to identity if.
switch (try parser.eventGetInternalType(e)) {
.abort_signal => {
return .{ .AbortSignal = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) };
return .{ .node = .{ .AbortSignal = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) } };
},
.xhr_event => {
const XMLHttpRequestEventTarget = @import("../xhr/event_target.zig").XMLHttpRequestEventTarget;
const base: *XMLHttpRequestEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et)));
return .{ .xhr = @fieldParentPtr("proto", base) };
},
else => {
// some of these probably need to be special-cased like abort_signal
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));
return .{ .node = try nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))) };
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/browser/events/event.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub const Event = struct {

pub fn toInterface(evt: *parser.Event) !Union {
return switch (try parser.eventGetInternalType(evt)) {
.event, .abort_signal => .{ .Event = evt },
.event, .abort_signal, .xhr_event => .{ .Event = evt },
.custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* },
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },
Expand Down
1 change: 1 addition & 0 deletions src/browser/netsurf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ pub const EventType = enum(u8) {
mouse_event = 3,
error_event = 4,
abort_signal = 5,
xhr_event = 6,
};

pub const MutationEvent = c.dom_mutation_event;
Expand Down
2 changes: 1 addition & 1 deletion src/browser/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ const Script = struct {

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

// if self.src is null, then this is an inline script, and it should
Expand Down
8 changes: 8 additions & 0 deletions src/browser/xhr/event_target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub const XMLHttpRequestEventTarget = struct {
onload_cbk: ?Function = null,
ontimeout_cbk: ?Function = null,
onloadend_cbk: ?Function = null,
onreadystatechange_cbk: ?Function = null,

fn register(
self: *XMLHttpRequestEventTarget,
Expand Down Expand Up @@ -86,6 +87,9 @@ pub const XMLHttpRequestEventTarget = struct {
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?Function {
return self.onloadend_cbk;
}
pub fn get_onreadystatechange(self: *XMLHttpRequestEventTarget) ?Function {
return self.onreadystatechange_cbk;
}

pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
if (self.onloadstart_cbk) |cbk| try self.unregister("loadstart", cbk.id);
Expand All @@ -111,4 +115,8 @@ pub const XMLHttpRequestEventTarget = struct {
if (self.onloadend_cbk) |cbk| try self.unregister("loadend", cbk.id);
self.onloadend_cbk = try self.register(page.arena, "loadend", listener);
}
pub fn set_onreadystatechange(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
if (self.onreadystatechange_cbk) |cbk| try self.unregister("readystatechange", cbk.id);
self.onreadystatechange_cbk = try self.register(page.arena, "readystatechange", listener);
}
};
55 changes: 51 additions & 4 deletions src/browser/xhr/xhr.zig
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ pub const XMLHttpRequest = struct {
done = 4,
};

// class attributes
pub const _UNSENT = @intFromEnum(State.unsent);
pub const _OPENED = @intFromEnum(State.opened);
pub const _HEADERS_RECEIVED = @intFromEnum(State.headers_received);
pub const _LOADING = @intFromEnum(State.loading);
pub const _DONE = @intFromEnum(State.done);

// https://xhr.spec.whatwg.org/#response-type
const ResponseType = enum {
Empty,
Expand Down Expand Up @@ -360,6 +367,8 @@ pub const XMLHttpRequest = struct {
// We can we defer event destroy once the event is dispatched.
defer parser.eventDestroy(evt);

try parser.eventSetInternalType(evt, .xhr_event);

try parser.eventInit(evt, typ, .{ .bubbles = true, .cancelable = true });
_ = try parser.eventTargetDispatchEvent(@as(*parser.EventTarget, @ptrCast(self)), evt);
}
Expand Down Expand Up @@ -580,11 +589,27 @@ pub const XMLHttpRequest = struct {
}

fn onErr(self: *XMLHttpRequest, err: anyerror) void {
self.state = .done;
self.send_flag = false;
self.dispatchEvt("readystatechange");
self.dispatchProgressEvent("error", .{});
self.dispatchProgressEvent("loadend", .{});

// capture the state before we change it
const s = self.state;

const is_abort = err == DOMError.Abort;

if (is_abort) {
self.state = .unsent;
} else {
self.state = .done;
self.dispatchEvt("error");
}

if (s != .done or s != .unsent) {
self.dispatchEvt("readystatechange");
if (is_abort) {
self.dispatchProgressEvent("abort", .{});
}
self.dispatchProgressEvent("loadend", .{});
}

const level: log.Level = if (err == DOMError.Abort) .debug else .err;
log.log(.http, level, "error", .{
Expand Down Expand Up @@ -923,4 +948,26 @@ test "Browser.XHR.XMLHttpRequest" {
// So the url has been retrieved.
.{ "status", "200" },
}, .{});

try runner.testCases(&.{
.{ "const req6 = new XMLHttpRequest()", null },
.{
\\ var readyStates = [];
\\ var currentTarget = null;
\\ req6.onreadystatechange = (e) => {
\\ currentTarget = e.currentTarget;
\\ readyStates.push(req6.readyState);
\\ }
,
null,
},
.{ "req6.open('GET', 'https://127.0.0.1:9581/xhr')", null },
.{ "req6.send()", null },
.{ "readyStates.length", "4" },
.{ "readyStates[0] === XMLHttpRequest.OPENED", "true" },
.{ "readyStates[1] === XMLHttpRequest.HEADERS_RECEIVED", "true" },
.{ "readyStates[2] === XMLHttpRequest.LOADING", "true" },
.{ "readyStates[3] === XMLHttpRequest.DONE", "true" },
.{ "currentTarget == req6", "true" },
}, .{});
}