Skip to content

Commit de71b97

Browse files
committed
optional listener, or object listener
1 parent 21d008c commit de71b97

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

src/browser/tests/events.html

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,65 @@
285285
testing.expectEqual('window-capture', non_bubble_calls[0]);
286286
testing.expectEqual('child', non_bubble_calls[1]);
287287
</script>
288+
289+
<script id=nullCallback>
290+
// Test that null callback is handled gracefully
291+
let nullTestPassed = false;
292+
window.addEventListener('testnull', null);
293+
window.removeEventListener('testnull', null);
294+
nullTestPassed = true;
295+
testing.expectEqual(true, nullTestPassed);
296+
</script>
297+
298+
<script id=objectCallback>
299+
// Test object with handleEvent method
300+
let handleEventCalls = 0;
301+
const handler = {
302+
handleEvent: function(e) {
303+
handleEventCalls += 1;
304+
testing.expectEqual('customhandler', e.type);
305+
}
306+
};
307+
308+
window.addEventListener('customhandler', handler);
309+
window.dispatchEvent(new Event('customhandler'));
310+
testing.expectEqual(1, handleEventCalls);
311+
312+
window.dispatchEvent(new Event('customhandler'));
313+
testing.expectEqual(2, handleEventCalls);
314+
315+
// Remove using the same object
316+
window.removeEventListener('customhandler', handler);
317+
window.dispatchEvent(new Event('customhandler'));
318+
testing.expectEqual(2, handleEventCalls); // Should not increment
319+
</script>
320+
321+
<script id=objectWithoutHandleEvent>
322+
// Test object without handleEvent (should be ignored)
323+
const badHandler = { foo: 'bar' };
324+
let badHandlerTestPassed = false;
325+
window.addEventListener('testbad', badHandler);
326+
// Event should not be triggered since there's no valid handler
327+
window.dispatchEvent(new Event('testbad'));
328+
badHandlerTestPassed = true;
329+
testing.expectEqual(true, badHandlerTestPassed);
330+
</script>
331+
332+
<script id=passiveDetection>
333+
// Test passive event listener detection pattern (common in polyfills)
334+
let passiveSupported = false;
335+
try {
336+
const opts = {};
337+
Object.defineProperty(opts, 'passive', {
338+
get: function() {
339+
passiveSupported = true;
340+
}
341+
});
342+
window.addEventListener('testpassive', opts, opts);
343+
window.removeEventListener('testpassive', opts, opts);
344+
} catch (e) {
345+
passiveSupported = false;
346+
}
347+
348+
testing.expectEqual(true, passiveSupported);
349+
</script>

src/browser/webapi/EventTarget.zig

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,38 +23,57 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
2323
return !event._cancelable or !event._prevent_default;
2424
}
2525

26-
const addEventListenerOptions = union(enum) {
26+
const AddEventListenerOptions = union(enum) {
2727
capture: bool,
2828
options: RegisterOptions,
2929
};
30-
pub fn addEventListener(self: *EventTarget, typ: []const u8, callback: js.Function, opts_: ?addEventListenerOptions, page: *Page) !void {
30+
31+
pub const EventListenerCallback = union(enum) {
32+
function: js.Function,
33+
object: js.Object,
34+
};
35+
pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?AddEventListenerOptions, page: *Page) !void {
36+
const callback = callback_ orelse return;
37+
38+
const actual_callback = switch (callback) {
39+
.function => |func| func,
40+
.object => |obj| (try obj.getFunction("handleEvent")) orelse return,
41+
};
42+
3143
const options = blk: {
3244
const o = opts_ orelse break :blk RegisterOptions{};
3345
break :blk switch (o) {
3446
.options => |opts| opts,
3547
.capture => |capture| RegisterOptions{ .capture = capture },
3648
};
3749
};
38-
return page._event_manager.register(self, typ, callback, options);
50+
return page._event_manager.register(self, typ, actual_callback, options);
3951
}
4052

41-
const removeEventListenerOptions = union(enum) {
53+
const RemoveEventListenerOptions = union(enum) {
4254
capture: bool,
4355
options: Options,
4456

4557
const Options = struct {
4658
useCapture: bool = false,
4759
};
4860
};
49-
pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback: js.Function, opts_: ?removeEventListenerOptions, page: *Page) !void {
61+
pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?RemoveEventListenerOptions, page: *Page) !void {
62+
const callback = callback_ orelse return;
63+
64+
const actual_callback = switch (callback) {
65+
.function => |func| func,
66+
.object => |obj| (try obj.getFunction("handleEvent")) orelse return,
67+
};
68+
5069
const use_capture = blk: {
5170
const o = opts_ orelse break :blk false;
5271
break :blk switch (o) {
5372
.capture => |capture| capture,
5473
.options => |opts| opts.useCapture,
5574
};
5675
};
57-
return page._event_manager.remove(self, typ, callback, use_capture);
76+
return page._event_manager.remove(self, typ, actual_callback, use_capture);
5877
}
5978

6079
pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {

0 commit comments

Comments
 (0)