From e103ce0f395e14f2653459ceb99e53de1fcc1a10 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 26 Mar 2026 14:03:33 +0300 Subject: [PATCH] `FormDataEvent`: initial support --- src/browser/js/bridge.zig | 1 + src/browser/webapi/Event.zig | 2 + src/browser/webapi/event/FormDataEvent.zig | 95 ++++++++++++++++++++++ src/browser/webapi/net/FormData.zig | 14 +++- 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/browser/webapi/event/FormDataEvent.zig diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index fb49e01e..9d4d6310 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -853,6 +853,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/event/InputEvent.zig"), @import("../webapi/event/PromiseRejectionEvent.zig"), @import("../webapi/event/SubmitEvent.zig"), + @import("../webapi/event/FormDataEvent.zig"), @import("../webapi/MessageChannel.zig"), @import("../webapi/MessagePort.zig"), @import("../webapi/media/MediaError.zig"), diff --git a/src/browser/webapi/Event.zig b/src/browser/webapi/Event.zig index bbac984d..54a53d7e 100644 --- a/src/browser/webapi/Event.zig +++ b/src/browser/webapi/Event.zig @@ -77,6 +77,7 @@ pub const Type = union(enum) { ui_event: *@import("event/UIEvent.zig"), promise_rejection_event: *@import("event/PromiseRejectionEvent.zig"), submit_event: *@import("event/SubmitEvent.zig"), + form_data_event: *@import("event/FormDataEvent.zig"), }; pub const Options = struct { @@ -176,6 +177,7 @@ pub fn is(self: *Event, comptime T: type) ?*T { .pop_state_event => |e| return if (T == @import("event/PopStateEvent.zig")) e else null, .promise_rejection_event => |e| return if (T == @import("event/PromiseRejectionEvent.zig")) e else null, .submit_event => |e| return if (T == @import("event/SubmitEvent.zig")) e else null, + .form_data_event => |e| return if (T == @import("event/FormDataEvent.zig")) e else null, .ui_event => |e| { if (T == @import("event/UIEvent.zig")) { return e; diff --git a/src/browser/webapi/event/FormDataEvent.zig b/src/browser/webapi/event/FormDataEvent.zig new file mode 100644 index 00000000..6947aeab --- /dev/null +++ b/src/browser/webapi/event/FormDataEvent.zig @@ -0,0 +1,95 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const String = @import("../../../string.zig").String; +const Page = @import("../../Page.zig"); +const Session = @import("../../Session.zig"); +const js = @import("../../js/js.zig"); + +const Event = @import("../Event.zig"); +const UIEvent = @import("UIEvent.zig"); + +const FormData = @import("../net/FormData.zig"); + +/// https://developer.mozilla.org/en-US/docs/Web/API/FormDataEvent +const FormDataEvent = @This(); + +_proto: *Event, +_form_data: ?*FormData = null, + +const Options = Event.inheritOptions(FormDataEvent, struct { + formData: ?*FormData = null, +}); + +pub fn init(typ: []const u8, maybe_options: Options, page: *Page) !*FormDataEvent { + const arena = try page.getArena(.{ .debug = "FormDataEvent" }); + errdefer page.releaseArena(arena); + const type_string = try String.init(arena, typ, .{}); + return initWithTrusted(arena, type_string, maybe_options, false, page); +} + +pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*FormDataEvent { + const arena = try page.getArena(.{ .debug = "FormDataEvent.trusted" }); + errdefer page.releaseArena(arena); + return initWithTrusted(arena, typ, _opts, true, page); +} + +fn initWithTrusted(arena: Allocator, typ: String, maybe_options: ?Options, trusted: bool, page: *Page) !*FormDataEvent { + const options = maybe_options orelse Options{}; + + const event = try page._factory.event( + arena, + typ, + FormDataEvent{ + ._proto = undefined, + ._form_data = options.formData, + }, + ); + + Event.populatePrototypes(event, options, trusted); + return event; +} + +pub fn deinit(self: *FormDataEvent, shutdown: bool, session: *Session) void { + self._proto.deinit(shutdown, session); +} + +pub fn asEvent(self: *FormDataEvent) *Event { + return self._proto; +} + +pub fn getFormData(self: *const FormDataEvent) ?*FormData { + return self._form_data; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(FormDataEvent); + + pub const Meta = struct { + pub const name = "FormDataEvent"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + pub const weak = true; + pub const finalizer = bridge.finalizer(FormDataEvent.deinit); + }; + + pub const constructor = bridge.constructor(FormDataEvent.init, .{}); + pub const formData = bridge.accessor(FormDataEvent.getFormData, null, .{}); +}; diff --git a/src/browser/webapi/net/FormData.zig b/src/browser/webapi/net/FormData.zig index 69246f93..73a5a5c8 100644 --- a/src/browser/webapi/net/FormData.zig +++ b/src/browser/webapi/net/FormData.zig @@ -35,10 +35,22 @@ _arena: Allocator, _list: KeyValueList, pub fn init(form: ?*Form, submitter: ?*Element, page: *Page) !*FormData { - return page._factory.create(FormData{ + const form_data = try page._factory.create(FormData{ ._arena = page.arena, ._list = try collectForm(page.arena, form, submitter, page), }); + + // Dispatch `formdata` event if form provided. + if (form) |_form| { + const form_data_event = try (@import("../event/FormDataEvent.zig")).initTrusted( + comptime .wrap("formdata"), + .{ .bubbles = true, .cancelable = false, .formData = form_data }, + page, + ); + try page._event_manager.dispatch(_form.asNode().asEventTarget(), form_data_event.asEvent()); + } + + return form_data; } pub fn get(self: *const FormData, name: []const u8) ?[]const u8 {