Skip to content

Commit d243963

Browse files
Merge pull request #156 from lightpanda-io/events
First implementation of Event system
2 parents 6bac876 + 501b3ca commit d243963

File tree

8 files changed

+710
-2
lines changed

8 files changed

+710
-2
lines changed

src/apiweb.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ const Console = @import("jsruntime").Console;
44

55
const DOM = @import("dom/dom.zig");
66
const HTML = @import("html/html.zig");
7+
const Events = @import("events/event.zig");
78

89
pub const HTMLDocument = @import("html/document.zig").HTMLDocument;
910

1011
// Interfaces
1112
pub const Interfaces = generate.Tuple(.{
1213
Console,
1314
DOM.Interfaces,
15+
Events.Interfaces,
1416
HTML.Interfaces,
1517
});

src/dom/document.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ pub const Document = struct {
8181
return try parser.documentGetDoctype(self);
8282
}
8383

84+
pub fn _createEvent(_: *parser.Document, eventCstr: []const u8) !*parser.Event {
85+
// TODO: for now only "Event" constructor is supported
86+
// see table on https://dom.spec.whatwg.org/#dom-document-createevent $2
87+
if (std.ascii.eqlIgnoreCase(eventCstr, "Event") or std.ascii.eqlIgnoreCase(eventCstr, "Events")) {
88+
return try parser.eventCreate();
89+
}
90+
return parser.DOMError.NotSupported;
91+
}
92+
8493
pub fn _getElementById(self: *parser.Document, id: []const u8) !?ElementUnion {
8594
const e = try parser.documentGetElementById(self, id) orelse return null;
8695
return try Element.toInterface(e);

src/dom/event_target.zig

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,223 @@
1+
const std = @import("std");
2+
3+
const jsruntime = @import("jsruntime");
4+
const Callback = jsruntime.Callback;
5+
const JSObjectID = jsruntime.JSObjectID;
6+
const Case = jsruntime.test_utils.Case;
7+
const checkCases = jsruntime.test_utils.checkCases;
8+
19
const parser = @import("../netsurf.zig");
210

311
const DOMException = @import("exceptions.zig").DOMException;
12+
const Nod = @import("node.zig");
413

14+
// EventTarget interfaces
15+
pub const Union = Nod.Union;
16+
17+
// EventTarget implementation
518
pub const EventTarget = struct {
619
pub const Self = parser.EventTarget;
720
pub const Exception = DOMException;
821
pub const mem_guarantied = true;
22+
23+
pub fn toInterface(et: *parser.EventTarget) !Union {
24+
// NOTE: for now we state that all EventTarget are Nodes
25+
// TODO: handle other types (eg. Window)
26+
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));
27+
}
28+
29+
// JS funcs
30+
// --------
31+
32+
pub fn _addEventListener(
33+
self: *parser.EventTarget,
34+
alloc: std.mem.Allocator,
35+
eventType: []const u8,
36+
cbk: Callback,
37+
capture: ?bool,
38+
// TODO: hanle EventListenerOptions
39+
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
40+
) !void {
41+
42+
// check if event target has already this listener
43+
const lst = try parser.eventTargetHasListener(
44+
self,
45+
eventType,
46+
capture orelse false,
47+
cbk.id(),
48+
);
49+
if (lst != null) {
50+
return;
51+
}
52+
53+
try parser.eventTargetAddEventListener(
54+
self,
55+
alloc,
56+
eventType,
57+
cbk,
58+
capture orelse false,
59+
);
60+
}
61+
62+
pub fn _removeEventListener(
63+
self: *parser.EventTarget,
64+
alloc: std.mem.Allocator,
65+
eventType: []const u8,
66+
cbk_id: JSObjectID,
67+
capture: ?bool,
68+
// TODO: hanle EventListenerOptions
69+
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
70+
) !void {
71+
72+
// check if event target has already this listener
73+
const lst = try parser.eventTargetHasListener(
74+
self,
75+
eventType,
76+
capture orelse false,
77+
cbk_id.get(),
78+
);
79+
if (lst == null) {
80+
return;
81+
}
82+
83+
// remove listener
84+
try parser.eventTargetRemoveEventListener(
85+
self,
86+
alloc,
87+
eventType,
88+
lst.?,
89+
capture orelse false,
90+
);
91+
}
92+
93+
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
94+
return try parser.eventTargetDispatchEvent(self, event);
95+
}
96+
97+
pub fn deinit(self: *parser.EventTarget, alloc: std.mem.Allocator) void {
98+
parser.eventTargetRemoveAllEventListeners(self, alloc) catch unreachable;
99+
}
9100
};
101+
102+
// Tests
103+
// -----
104+
105+
pub fn testExecFn(
106+
_: std.mem.Allocator,
107+
js_env: *jsruntime.Env,
108+
) anyerror!void {
109+
var common = [_]Case{
110+
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
111+
.{ .src = "let para = document.getElementById('para')", .ex = "undefined" },
112+
// NOTE: as some event properties will change during the event dispatching phases
113+
// we need to copy thoses values in order to check them afterwards
114+
.{ .src =
115+
\\var nb = 0; var evt; var phase; var cur;
116+
\\function cbk(event) {
117+
\\evt = event;
118+
\\phase = event.eventPhase;
119+
\\cur = event.currentTarget;
120+
\\nb ++;
121+
\\}
122+
, .ex = "undefined" },
123+
};
124+
try checkCases(js_env, &common);
125+
126+
var basic = [_]Case{
127+
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
128+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
129+
.{ .src = "nb", .ex = "1" },
130+
.{ .src = "evt instanceof Event", .ex = "true" },
131+
.{ .src = "evt.type", .ex = "basic" },
132+
.{ .src = "phase", .ex = "2" },
133+
.{ .src = "cur.getAttribute('id')", .ex = "content" },
134+
};
135+
try checkCases(js_env, &basic);
136+
137+
var basic_child = [_]Case{
138+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
139+
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
140+
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
141+
.{ .src = "evt === undefined", .ex = "true" },
142+
};
143+
try checkCases(js_env, &basic_child);
144+
145+
var basic_twice = [_]Case{
146+
.{ .src = "nb = 0", .ex = "0" },
147+
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
148+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
149+
.{ .src = "nb", .ex = "1" },
150+
};
151+
try checkCases(js_env, &basic_twice);
152+
153+
var basic_twice_capture = [_]Case{
154+
.{ .src = "nb = 0", .ex = "0" },
155+
.{ .src = "content.addEventListener('basic', cbk, true)", .ex = "undefined" },
156+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
157+
.{ .src = "nb", .ex = "2" },
158+
};
159+
try checkCases(js_env, &basic_twice_capture);
160+
161+
var basic_remove = [_]Case{
162+
.{ .src = "nb = 0", .ex = "0" },
163+
.{ .src = "content.removeEventListener('basic', cbk)", .ex = "undefined" },
164+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
165+
.{ .src = "nb", .ex = "1" },
166+
};
167+
try checkCases(js_env, &basic_remove);
168+
169+
var basic_capture_remove = [_]Case{
170+
.{ .src = "nb = 0", .ex = "0" },
171+
.{ .src = "content.removeEventListener('basic', cbk, true)", .ex = "undefined" },
172+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
173+
.{ .src = "nb", .ex = "0" },
174+
};
175+
try checkCases(js_env, &basic_capture_remove);
176+
177+
var capture = [_]Case{
178+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
179+
.{ .src = "content.addEventListener('capture', cbk, true)", .ex = "undefined" },
180+
.{ .src = "content.dispatchEvent(new Event('capture'))", .ex = "true" },
181+
.{ .src = "nb", .ex = "1" },
182+
.{ .src = "evt instanceof Event", .ex = "true" },
183+
.{ .src = "evt.type", .ex = "capture" },
184+
.{ .src = "phase", .ex = "2" },
185+
.{ .src = "cur.getAttribute('id')", .ex = "content" },
186+
};
187+
try checkCases(js_env, &capture);
188+
189+
var capture_child = [_]Case{
190+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
191+
.{ .src = "para.dispatchEvent(new Event('capture'))", .ex = "true" },
192+
.{ .src = "nb", .ex = "1" },
193+
.{ .src = "evt instanceof Event", .ex = "true" },
194+
.{ .src = "evt.type", .ex = "capture" },
195+
.{ .src = "phase", .ex = "1" },
196+
.{ .src = "cur.getAttribute('id')", .ex = "content" },
197+
};
198+
try checkCases(js_env, &capture_child);
199+
200+
var bubbles = [_]Case{
201+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
202+
.{ .src = "content.addEventListener('bubbles', cbk)", .ex = "undefined" },
203+
.{ .src = "content.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" },
204+
.{ .src = "nb", .ex = "1" },
205+
.{ .src = "evt instanceof Event", .ex = "true" },
206+
.{ .src = "evt.type", .ex = "bubbles" },
207+
.{ .src = "evt.bubbles", .ex = "true" },
208+
.{ .src = "phase", .ex = "2" },
209+
.{ .src = "cur.getAttribute('id')", .ex = "content" },
210+
};
211+
try checkCases(js_env, &bubbles);
212+
213+
var bubbles_child = [_]Case{
214+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
215+
.{ .src = "para.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" },
216+
.{ .src = "nb", .ex = "1" },
217+
.{ .src = "evt instanceof Event", .ex = "true" },
218+
.{ .src = "evt.type", .ex = "bubbles" },
219+
.{ .src = "phase", .ex = "3" },
220+
.{ .src = "cur.getAttribute('id')", .ex = "content" },
221+
};
222+
try checkCases(js_env, &bubbles_child);
223+
}

0 commit comments

Comments
 (0)