diff --git a/src/apiweb.zig b/src/apiweb.zig index 08f4c3f5..40cc8779 100644 --- a/src/apiweb.zig +++ b/src/apiweb.zig @@ -4,6 +4,7 @@ const Console = @import("jsruntime").Console; const DOM = @import("dom/dom.zig"); const HTML = @import("html/html.zig"); +const Events = @import("events/event.zig"); pub const HTMLDocument = @import("html/document.zig").HTMLDocument; @@ -11,5 +12,6 @@ pub const HTMLDocument = @import("html/document.zig").HTMLDocument; pub const Interfaces = generate.Tuple(.{ Console, DOM.Interfaces, + Events.Interfaces, HTML.Interfaces, }); diff --git a/src/dom/document.zig b/src/dom/document.zig index 702d92e2..7f3af5cf 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -81,6 +81,15 @@ pub const Document = struct { return try parser.documentGetDoctype(self); } + pub fn _createEvent(_: *parser.Document, eventCstr: []const u8) !*parser.Event { + // TODO: for now only "Event" constructor is supported + // see table on https://dom.spec.whatwg.org/#dom-document-createevent $2 + if (std.ascii.eqlIgnoreCase(eventCstr, "Event") or std.ascii.eqlIgnoreCase(eventCstr, "Events")) { + return try parser.eventCreate(); + } + return parser.DOMError.NotSupported; + } + pub fn _getElementById(self: *parser.Document, id: []const u8) !?ElementUnion { const e = try parser.documentGetElementById(self, id) orelse return null; return try Element.toInterface(e); diff --git a/src/dom/event_target.zig b/src/dom/event_target.zig index 9058f9c0..84e53b40 100644 --- a/src/dom/event_target.zig +++ b/src/dom/event_target.zig @@ -1,9 +1,223 @@ +const std = @import("std"); + +const jsruntime = @import("jsruntime"); +const Callback = jsruntime.Callback; +const JSObjectID = jsruntime.JSObjectID; +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const parser = @import("../netsurf.zig"); const DOMException = @import("exceptions.zig").DOMException; +const Nod = @import("node.zig"); +// EventTarget interfaces +pub const Union = Nod.Union; + +// EventTarget implementation pub const EventTarget = struct { pub const Self = parser.EventTarget; pub const Exception = DOMException; pub const mem_guarantied = true; + + pub fn toInterface(et: *parser.EventTarget) !Union { + // NOTE: for now we state that all EventTarget are Nodes + // TODO: handle other types (eg. Window) + return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))); + } + + // JS funcs + // -------- + + pub fn _addEventListener( + self: *parser.EventTarget, + alloc: std.mem.Allocator, + eventType: []const u8, + cbk: Callback, + capture: ?bool, + // TODO: hanle EventListenerOptions + // see #https://github.com/lightpanda-io/jsruntime-lib/issues/114 + ) !void { + + // check if event target has already this listener + const lst = try parser.eventTargetHasListener( + self, + eventType, + capture orelse false, + cbk.id(), + ); + if (lst != null) { + return; + } + + try parser.eventTargetAddEventListener( + self, + alloc, + eventType, + cbk, + capture orelse false, + ); + } + + pub fn _removeEventListener( + self: *parser.EventTarget, + alloc: std.mem.Allocator, + eventType: []const u8, + cbk_id: JSObjectID, + capture: ?bool, + // TODO: hanle EventListenerOptions + // see #https://github.com/lightpanda-io/jsruntime-lib/issues/114 + ) !void { + + // check if event target has already this listener + const lst = try parser.eventTargetHasListener( + self, + eventType, + capture orelse false, + cbk_id.get(), + ); + if (lst == null) { + return; + } + + // remove listener + try parser.eventTargetRemoveEventListener( + self, + alloc, + eventType, + lst.?, + capture orelse false, + ); + } + + pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool { + return try parser.eventTargetDispatchEvent(self, event); + } + + pub fn deinit(self: *parser.EventTarget, alloc: std.mem.Allocator) void { + parser.eventTargetRemoveAllEventListeners(self, alloc) catch unreachable; + } }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var common = [_]Case{ + .{ .src = "let content = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "let para = document.getElementById('para')", .ex = "undefined" }, + // NOTE: as some event properties will change during the event dispatching phases + // we need to copy thoses values in order to check them afterwards + .{ .src = + \\var nb = 0; var evt; var phase; var cur; + \\function cbk(event) { + \\evt = event; + \\phase = event.eventPhase; + \\cur = event.currentTarget; + \\nb ++; + \\} + , .ex = "undefined" }, + }; + try checkCases(js_env, &common); + + var basic = [_]Case{ + .{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt instanceof Event", .ex = "true" }, + .{ .src = "evt.type", .ex = "basic" }, + .{ .src = "phase", .ex = "2" }, + .{ .src = "cur.getAttribute('id')", .ex = "content" }, + }; + try checkCases(js_env, &basic); + + var basic_child = [_]Case{ + .{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" }, + .{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling + .{ .src = "evt === undefined", .ex = "true" }, + }; + try checkCases(js_env, &basic_child); + + var basic_twice = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + }; + try checkCases(js_env, &basic_twice); + + var basic_twice_capture = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = "content.addEventListener('basic', cbk, true)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "2" }, + }; + try checkCases(js_env, &basic_twice_capture); + + var basic_remove = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = "content.removeEventListener('basic', cbk)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + }; + try checkCases(js_env, &basic_remove); + + var basic_capture_remove = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = "content.removeEventListener('basic', cbk, true)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" }, + .{ .src = "nb", .ex = "0" }, + }; + try checkCases(js_env, &basic_capture_remove); + + var capture = [_]Case{ + .{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" }, + .{ .src = "content.addEventListener('capture', cbk, true)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('capture'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt instanceof Event", .ex = "true" }, + .{ .src = "evt.type", .ex = "capture" }, + .{ .src = "phase", .ex = "2" }, + .{ .src = "cur.getAttribute('id')", .ex = "content" }, + }; + try checkCases(js_env, &capture); + + var capture_child = [_]Case{ + .{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" }, + .{ .src = "para.dispatchEvent(new Event('capture'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt instanceof Event", .ex = "true" }, + .{ .src = "evt.type", .ex = "capture" }, + .{ .src = "phase", .ex = "1" }, + .{ .src = "cur.getAttribute('id')", .ex = "content" }, + }; + try checkCases(js_env, &capture_child); + + var bubbles = [_]Case{ + .{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" }, + .{ .src = "content.addEventListener('bubbles', cbk)", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt instanceof Event", .ex = "true" }, + .{ .src = "evt.type", .ex = "bubbles" }, + .{ .src = "evt.bubbles", .ex = "true" }, + .{ .src = "phase", .ex = "2" }, + .{ .src = "cur.getAttribute('id')", .ex = "content" }, + }; + try checkCases(js_env, &bubbles); + + var bubbles_child = [_]Case{ + .{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" }, + .{ .src = "para.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt instanceof Event", .ex = "true" }, + .{ .src = "evt.type", .ex = "bubbles" }, + .{ .src = "phase", .ex = "3" }, + .{ .src = "cur.getAttribute('id')", .ex = "content" }, + }; + try checkCases(js_env, &bubbles_child); +} diff --git a/src/events/event.zig b/src/events/event.zig new file mode 100644 index 00000000..fe939621 --- /dev/null +++ b/src/events/event.zig @@ -0,0 +1,201 @@ +const std = @import("std"); + +const generate = @import("../generate.zig"); + +const jsruntime = @import("jsruntime"); +const Callback = jsruntime.Callback; +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + +const parser = @import("../netsurf.zig"); + +const DOMException = @import("../dom/exceptions.zig").DOMException; +const EventTarget = @import("../dom/event_target.zig").EventTarget; +const EventTargetUnion = @import("../dom/event_target.zig").Union; + +// https://dom.spec.whatwg.org/#event +pub const Event = struct { + pub const Self = parser.Event; + pub const Exception = DOMException; + pub const mem_guarantied = true; + + pub const EventInit = parser.EventInit; + + // JS + // -- + + pub const _CAPTURING_PHASE = 1; + pub const _AT_TARGET = 2; + pub const _BUBBLING_PHASE = 3; + + pub fn constructor(eventType: []const u8, opts: ?EventInit) !*parser.Event { + const event = try parser.eventCreate(); + try parser.eventInit(event, eventType, opts orelse EventInit{}); + return event; + } + + // Getters + + pub fn get_type(self: *parser.Event) ![]const u8 { + return try parser.eventType(self); + } + + pub fn get_target(self: *parser.Event) !?EventTargetUnion { + const et = try parser.eventTarget(self); + if (et == null) return null; + return try EventTarget.toInterface(et.?); + } + + pub fn get_currentTarget(self: *parser.Event) !?EventTargetUnion { + const et = try parser.eventCurrentTarget(self); + if (et == null) return null; + return try EventTarget.toInterface(et.?); + } + + pub fn get_eventPhase(self: *parser.Event) !u8 { + return try parser.eventPhase(self); + } + + pub fn get_bubbles(self: *parser.Event) !bool { + return try parser.eventBubbles(self); + } + + pub fn get_cancelable(self: *parser.Event) !bool { + return try parser.eventCancelable(self); + } + + pub fn get_defaultPrevented(self: *parser.Event) !bool { + return try parser.eventDefaultPrevented(self); + } + + pub fn get_isTrusted(self: *parser.Event) !bool { + return try parser.eventIsTrusted(self); + } + + pub fn get_timestamp(self: *parser.Event) !u32 { + return try parser.eventTimestamp(self); + } + + // Methods + + pub fn _initEvent( + self: *parser.Event, + eventType: []const u8, + bubbles: ?bool, + cancelable: ?bool, + ) !void { + const opts = EventInit{ + .bubbles = bubbles orelse false, + .cancelable = cancelable orelse false, + }; + return try parser.eventInit(self, eventType, opts); + } + + pub fn _stopPropagation(self: *parser.Event) !void { + return try parser.eventStopPropagation(self); + } + + pub fn _stopImmediatePropagation(self: *parser.Event) !void { + return try parser.eventStopImmediatePropagation(self); + } + + pub fn _preventDefault(self: *parser.Event) !void { + return try parser.eventPreventDefault(self); + } +}; + +// Event interfaces +pub const Interfaces = generate.Tuple(.{ + Event, +}); +const Generated = generate.Union.compile(Interfaces); +pub const Union = Generated._union; + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var common = [_]Case{ + .{ .src = "let content = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "let para = document.getElementById('para')", .ex = "undefined" }, + .{ .src = "var nb = 0; var evt", .ex = "undefined" }, + }; + try checkCases(js_env, &common); + + var basic = [_]Case{ + .{ .src = + \\content.addEventListener('target', + \\function(e) { + \\evt = e; nb = nb + 1; + \\e.preventDefault(); + \\}) + , .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('target', {bubbles: true, cancelable: true}))", .ex = "false" }, + .{ .src = "nb", .ex = "1" }, + .{ .src = "evt.target === content", .ex = "true" }, + .{ .src = "evt.bubbles", .ex = "true" }, + .{ .src = "evt.cancelable", .ex = "true" }, + .{ .src = "evt.defaultPrevented", .ex = "true" }, + .{ .src = "evt.isTrusted", .ex = "true" }, + .{ .src = "evt.timestamp > 1704063600", .ex = "true" }, // 2024/01/01 00:00 + // event.type, event.currentTarget, event.phase checked in EventTarget + }; + try checkCases(js_env, &basic); + + var stop = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = + \\content.addEventListener('stop', + \\function(e) { + \\e.stopPropagation(); + \\nb = nb + 1; + \\}, true) + , .ex = "undefined" }, + // the following event listener will not be invoked + .{ .src = + \\para.addEventListener('stop', + \\function(e) { + \\nb = nb + 1; + \\}) + , .ex = "undefined" }, + .{ .src = "para.dispatchEvent(new Event('stop'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, // will be 2 if event was not stopped at content event listener + }; + try checkCases(js_env, &stop); + + var stop_immediate = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = + \\content.addEventListener('immediate', + \\function(e) { + \\e.stopImmediatePropagation(); + \\nb = nb + 1; + \\}) + , .ex = "undefined" }, + // the following event listener will not be invoked + .{ .src = + \\content.addEventListener('immediate', + \\function(e) { + \\nb = nb + 1; + \\}) + , .ex = "undefined" }, + .{ .src = "content.dispatchEvent(new Event('immediate'))", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, // will be 2 if event was not stopped at first content event listener + }; + try checkCases(js_env, &stop_immediate); + + var legacy = [_]Case{ + .{ .src = "nb = 0", .ex = "0" }, + .{ .src = + \\content.addEventListener('legacy', + \\function(e) { + \\evt = e; nb = nb + 1; + \\}) + , .ex = "undefined" }, + .{ .src = "let evtLegacy = document.createEvent('Event')", .ex = "undefined" }, + .{ .src = "evtLegacy.initEvent('legacy')", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(evtLegacy)", .ex = "true" }, + .{ .src = "nb", .ex = "1" }, + }; + try checkCases(js_env, &legacy); +} diff --git a/src/netsurf.zig b/src/netsurf.zig index 7d5802b1..7b40363c 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -5,6 +5,8 @@ const c = @cImport({ @cInclude("dom/bindings/hubbub/parser.h"); }); +const Callback = @import("jsruntime").Callback; + // Vtable // ------ @@ -341,9 +343,285 @@ fn DOMErr(except: DOMException) DOMError!void { }; } +// Event +pub const Event = c.dom_event; + +pub fn eventCreate() !*Event { + var evt: ?*Event = undefined; + const err = c._dom_event_create(&evt); + try DOMErr(err); + return evt.?; +} + +pub const EventInit = struct { + bubbles: bool = false, + cancelable: bool = false, + composed: bool = false, +}; + +pub fn eventInit(evt: *Event, typ: []const u8, opts: EventInit) !void { + const s = try strFromData(typ); + const err = c._dom_event_init(evt, s, opts.bubbles, opts.cancelable); + try DOMErr(err); +} + +pub fn eventType(evt: *Event) ![]const u8 { + var s: ?*String = undefined; + const err = c._dom_event_get_type(evt, &s); + try DOMErr(err); + return strToData(s.?); +} + +pub fn eventTarget(evt: *Event) !?*EventTarget { + var et: ?*EventTarget = undefined; + const err = c._dom_event_get_target(evt, &et); + try DOMErr(err); + return et; +} + +pub fn eventCurrentTarget(evt: *Event) !?*EventTarget { + var et: ?*EventTarget = undefined; + const err = c._dom_event_get_current_target(evt, &et); + try DOMErr(err); + return et; +} + +pub fn eventPhase(evt: *Event) !u8 { + var phase: c.dom_event_flow_phase = undefined; + const err = c._dom_event_get_event_phase(evt, &phase); + try DOMErr(err); + return @as(u8, @intCast(phase)); +} + +pub fn eventBubbles(evt: *Event) !bool { + var res: bool = undefined; + const err = c._dom_event_get_bubbles(evt, &res); + try DOMErr(err); + return res; +} + +pub fn eventCancelable(evt: *Event) !bool { + var res: bool = undefined; + const err = c._dom_event_get_cancelable(evt, &res); + try DOMErr(err); + return res; +} + +pub fn eventDefaultPrevented(evt: *Event) !bool { + var res: bool = undefined; + const err = c._dom_event_is_default_prevented(evt, &res); + try DOMErr(err); + return res; +} + +pub fn eventIsTrusted(evt: *Event) !bool { + var res: bool = undefined; + const err = c._dom_event_get_is_trusted(evt, &res); + try DOMErr(err); + return res; +} + +pub fn eventTimestamp(evt: *Event) !u32 { + var ts: c_uint = undefined; + const err = c._dom_event_get_timestamp(evt, &ts); + try DOMErr(err); + return @as(u32, @intCast(ts)); +} + +pub fn eventStopPropagation(evt: *Event) !void { + const err = c._dom_event_stop_propagation(evt); + try DOMErr(err); +} + +pub fn eventStopImmediatePropagation(evt: *Event) !void { + const err = c._dom_event_stop_immediate_propagation(evt); + try DOMErr(err); +} + +pub fn eventPreventDefault(evt: *Event) !void { + const err = c._dom_event_prevent_default(evt); + try DOMErr(err); +} + +// EventHandler +fn event_handler_cbk(data: *anyopaque) *Callback { + const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data); + return @as(*Callback, @ptrCast(ptr)); +} + +const event_handler = struct { + fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void { + if (data) |d| { + const func = event_handler_cbk(d); + func.call(.{event}) catch unreachable; + // NOTE: we can not call func.deinit here + // b/c the handler can be called several times + // either on this dispatch event or in anoter one + } + } +}.handle; + +// EventListener +pub const EventListener = c.dom_event_listener; +const EventListenerEntry = c.listener_entry; + +fn eventListenerGetData(lst: *EventListener) ?*anyopaque { + return c.dom_event_listener_get_data(lst); +} + // EventTarget pub const EventTarget = c.dom_event_target; +fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable { + return getVtable(c.dom_event_target_vtable, EventTarget, et); +} + +pub fn eventTargetHasListener( + et: *EventTarget, + typ: []const u8, + capture: bool, + cbk_id: usize, +) !?*EventListener { + const str = try strFromData(typ); + + var current: ?*EventListenerEntry = null; + var next: ?*EventListenerEntry = undefined; + var lst: ?*EventListener = undefined; + + // iterate over the EventTarget's listeners + while (true) { + const err = eventTargetVtable(et).iter_event_listener.?( + et, + str, + capture, + current, + &next, + &lst, + ); + try DOMErr(err); + + if (lst) |listener| { + // the EventTarget has a listener for this event type + // and capture property, + // let's check if the callback handler is the same + defer c.dom_event_listener_unref(listener); + const data = eventListenerGetData(listener); + if (data) |d| { + const cbk = event_handler_cbk(d); + if (cbk_id == cbk.id()) { + return lst; + } + } + } + + if (next == null) { + // no more listeners, end of the iteration + break; + } + + // next iteration + current = next; + } + + return null; +} + +pub fn eventTargetAddEventListener( + et: *EventTarget, + alloc: std.mem.Allocator, + typ: []const u8, + cbk: Callback, + capture: bool, +) !void { + // this allocation will be removed either on + // eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners + const cbk_ptr = try alloc.create(Callback); + cbk_ptr.* = cbk; + + const ctx = @as(*anyopaque, @ptrCast(cbk_ptr)); + var listener: ?*EventListener = undefined; + const errLst = c.dom_event_listener_create(event_handler, ctx, &listener); + try DOMErr(errLst); + defer c.dom_event_listener_unref(listener); + + const s = try strFromData(typ); + const err = eventTargetVtable(et).add_event_listener.?(et, s, listener, capture); + try DOMErr(err); +} + +pub fn eventTargetRemoveEventListener( + et: *EventTarget, + alloc: std.mem.Allocator, + typ: []const u8, + lst: *EventListener, + capture: bool, +) !void { + const data = eventListenerGetData(lst); + // free cbk allocation made on eventTargetAddEventListener + if (data) |d| { + const cbk_ptr = event_handler_cbk(d); + cbk_ptr.deinit(alloc); + alloc.destroy(cbk_ptr); + } + + const s = try strFromData(typ); + const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture); + try DOMErr(err); +} + +pub fn eventTargetRemoveAllEventListeners( + et: *EventTarget, + alloc: std.mem.Allocator, +) !void { + var next: ?*EventListenerEntry = undefined; + var lst: ?*EventListener = undefined; + + // iterate over the EventTarget's listeners + while (true) { + const errIter = eventTargetVtable(et).iter_event_listener.?( + et, + null, + false, + null, + &next, + &lst, + ); + try DOMErr(errIter); + + if (lst) |listener| { + defer c.dom_event_listener_unref(listener); + const data = eventListenerGetData(listener); + if (data) |d| { + // free cbk allocation made on eventTargetAddEventListener + const cbk = event_handler_cbk(d); + cbk.deinit(alloc); + alloc.destroy(cbk); + } + const err = eventTargetVtable(et).remove_event_listener.?( + et, + null, + lst, + false, + ); + try DOMErr(err); + } + + if (next == null) { + // no more listeners, end of the iteration + break; + } + + // next iteration + } +} + +pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool { + var res: bool = undefined; + const err = eventTargetVtable(et).dispatch_event.?(et, event, &res); + try DOMErr(err); + return res; +} + // NodeType pub const NodeType = enum(u4) { diff --git a/src/run_tests.zig b/src/run_tests.zig index 3a8fc499..22adffe9 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -20,6 +20,8 @@ const NamedNodeMapExecFn = @import("dom/namednodemap.zig").testExecFn; const DOMTokenListExecFn = @import("dom/token_list.zig").testExecFn; const NodeListTestExecFn = @import("dom/nodelist.zig").testExecFn; const AttrTestExecFn = @import("dom/attribute.zig").testExecFn; +const EventTargetTestExecFn = @import("dom/event_target.zig").testExecFn; +const EventTestExecFn = @import("events/event.zig").testExecFn; pub const Types = jsruntime.reflect(apiweb.Interfaces); @@ -73,6 +75,8 @@ fn testsAllExecFn( DOMTokenListExecFn, NodeListTestExecFn, AttrTestExecFn, + EventTargetTestExecFn, + EventTestExecFn, }; inline for (testFns) |testFn| { diff --git a/tests/wpt b/tests/wpt index c9e76582..a2c7f5a2 160000 --- a/tests/wpt +++ b/tests/wpt @@ -1 +1 @@ -Subproject commit c9e7658223455f1e3ce11d348cb79528326c7c4c +Subproject commit a2c7f5a24dd5269e8475fc3cae18c4719058dedc diff --git a/vendor/jsruntime-lib b/vendor/jsruntime-lib index 7de65ccd..2af9e9b7 160000 --- a/vendor/jsruntime-lib +++ b/vendor/jsruntime-lib @@ -1 +1 @@ -Subproject commit 7de65ccd304b25a8db69c57787bf28cd3b188b90 +Subproject commit 2af9e9b7fc0ac94918d32a44822f050f939e47b6