CustomEvent and document.createEvent

This commit is contained in:
Karl Seguin
2025-11-22 12:33:29 +08:00
parent 3c010f0e73
commit 6b990f8f12
5 changed files with 196 additions and 0 deletions

View File

@@ -544,6 +544,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/encoding/TextDecoder.zig"),
@import("../webapi/encoding/TextEncoder.zig"),
@import("../webapi/Event.zig"),
@import("../webapi/event/CustomEvent.zig"),
@import("../webapi/event/ErrorEvent.zig"),
@import("../webapi/event/ProgressEvent.zig"),
@import("../webapi/EventTarget.zig"),

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=customEventConstructor>
{
const event = new CustomEvent('test');
testing.expectEqual('test', event.type);
testing.expectEqual(null, event.detail);
testing.expectEqual(false, event.bubbles);
testing.expectEqual(false, event.cancelable);
}
</script>
<script id=customEventWithDetail>
{
const detail = { foo: 'bar', num: 42 };
const event = new CustomEvent('custom', { detail });
testing.expectEqual('custom', event.type);
testing.expectEqual('bar', event.detail.foo);
testing.expectEqual(42, event.detail.num);
}
</script>
<script id=customEventWithOptions>
{
const event = new CustomEvent('opts', {
bubbles: true,
cancelable: true,
detail: 'test-detail'
});
testing.expectEqual(true, event.bubbles);
testing.expectEqual(true, event.cancelable);
testing.expectEqual('test-detail', event.detail);
}
</script>
<script id=customEventDispatch>
{
const target = document.createElement('div');
let receivedEvent = null;
target.addEventListener('myevent', (e) => {
receivedEvent = e;
});
const event = new CustomEvent('myevent', {
detail: { message: 'hello' }
});
target.dispatchEvent(event);
testing.expectEqual('hello', receivedEvent.detail.message);
}
</script>
<script id=createEventCustomEvent>
{
const event = document.createEvent('CustomEvent');
testing.expectEqual('', event.type);
testing.expectEqual(null, event.detail);
}
</script>
<script id=createEventCaseInsensitive>
{
const event1 = document.createEvent('customevent');
testing.expectEqual('', event1.type);
const event2 = document.createEvent('CUSTOMEVENT');
testing.expectEqual('', event2.type);
const event3 = document.createEvent('CustomEvents');
testing.expectEqual('', event3.type);
}
</script>
<script id=createEventGeneric>
{
const event1 = document.createEvent('Event');
testing.expectEqual('', event1.type);
const event2 = document.createEvent('Events');
testing.expectEqual('', event2.type);
const event3 = document.createEvent('HTMLEvents');
testing.expectEqual('', event3.type);
}
</script>
<script id=customEventInheritance>
{
const event = new CustomEvent('inherit-test');
testing.expectEqual(Event.NONE, event.eventPhase);
testing.expectEqual(false, event.defaultPrevented);
}
</script>

View File

@@ -175,6 +175,26 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node
return page.createTextNode(data);
}
pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@import("Event.zig") {
const Event = @import("Event.zig");
if (std.ascii.eqlIgnoreCase(event_type, "event") or std.ascii.eqlIgnoreCase(event_type, "events") or std.ascii.eqlIgnoreCase(event_type, "htmlevents")) {
return Event.init("", null, page);
}
if (std.ascii.eqlIgnoreCase(event_type, "customevent") or std.ascii.eqlIgnoreCase(event_type, "customevents")) {
const CustomEvent = @import("event/CustomEvent.zig");
const custom_event = try CustomEvent.init("", null, page);
return custom_event.asEvent();
}
if (std.ascii.eqlIgnoreCase(event_type, "messageevent")) {
return error.NotSupported;
}
return error.NotSupported;
}
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
const show = what_to_show orelse NodeFilter.SHOW_ALL;
return DOMTreeWalker.init(root, show, filter, page);
@@ -239,6 +259,7 @@ pub const JsApi = struct {
pub const createDocumentFragment = bridge.function(Document.createDocumentFragment, .{});
pub const createComment = bridge.function(Document.createComment, .{});
pub const createTextNode = bridge.function(Document.createTextNode, .{});
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
pub const createNodeIterator = bridge.function(Document.createNodeIterator, .{});
pub const getElementById = bridge.function(Document.getElementById, .{});

View File

@@ -48,6 +48,7 @@ pub const Type = union(enum) {
generic,
progress_event: *@import("event/ProgressEvent.zig"),
error_event: *@import("event/ErrorEvent.zig"),
custom_event: *@import("event/CustomEvent.zig"),
};
const Options = struct {

View File

@@ -0,0 +1,78 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
const CustomEvent = @This();
_proto: *Event,
_detail: ?js.Object = null,
_arena: Allocator,
pub const InitOptions = struct {
detail: ?js.Object = null,
bubbles: bool = false,
cancelable: bool = false,
};
pub fn init(typ: []const u8, opts_: ?InitOptions, page: *Page) !*CustomEvent {
const arena = page.arena;
const opts = opts_ orelse InitOptions{};
const event = try page._factory.event(typ, CustomEvent{
._arena = arena,
._proto = undefined,
._detail = if (opts.detail) |detail| try detail.persist() else null,
});
event._proto._bubbles = opts.bubbles;
event._proto._cancelable = opts.cancelable;
return event;
}
pub fn asEvent(self: *CustomEvent) *Event {
return self._proto;
}
pub fn getDetail(self: *const CustomEvent) ?js.Object {
return self._detail;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(CustomEvent);
pub const Meta = struct {
pub const name = "CustomEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const constructor = bridge.constructor(CustomEvent.init, .{});
pub const detail = bridge.accessor(CustomEvent.getDetail, null, .{});
};
const testing = @import("../../../testing.zig");
test "WebApi: CustomEvent" {
try testing.htmlRunner("event/custom_event.html", .{});
}