diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 37626975..7d1b18c3 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -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 { @@ -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))) }; }, } } diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index 9cdc4a94..3feed029 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -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)) }, diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 8fdc2f1b..3be9481a 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -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; diff --git a/src/browser/page.zig b/src/browser/page.zig index e8f92216..5e43f2d5 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -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 diff --git a/src/browser/xhr/event_target.zig b/src/browser/xhr/event_target.zig index 8f4b9e28..f664a3a6 100644 --- a/src/browser/xhr/event_target.zig +++ b/src/browser/xhr/event_target.zig @@ -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, @@ -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); @@ -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); + } }; diff --git a/src/browser/xhr/xhr.zig b/src/browser/xhr/xhr.zig index e7b11630..0c876c93 100644 --- a/src/browser/xhr/xhr.zig +++ b/src/browser/xhr/xhr.zig @@ -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, @@ -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); } @@ -923,4 +932,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" }, + }, .{}); }