From 267eee9693573a085188a15938451eef726c8736 Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Tue, 24 Mar 2026 14:19:50 +0100 Subject: [PATCH] Fix Form.requestSubmit(submitter) not setting SubmitEvent.submitter Create SubmitEvent type and use it in submitForm() so that e.submitter is correctly set when requestSubmit(submitter) is called. Fixes #1982 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/browser/Page.zig | 4 +- src/browser/js/bridge.zig | 1 + src/browser/tests/element/html/form.html | 41 ++++++++++ src/browser/webapi/Event.zig | 1 + src/browser/webapi/event/SubmitEvent.zig | 95 ++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/browser/webapi/event/SubmitEvent.zig diff --git a/src/browser/Page.zig b/src/browser/Page.zig index bfff78ce..42e65a52 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -61,6 +61,7 @@ const IntersectionObserver = @import("webapi/IntersectionObserver.zig"); const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig"); const storage = @import("webapi/storage/storage.zig"); const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig"); +const SubmitEvent = @import("webapi/event/SubmitEvent.zig"); const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind; const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig"); const MouseEvent = @import("webapi/event/MouseEvent.zig"); @@ -3487,7 +3488,8 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form }; if (submit_opts.fire_event) { - const submit_event = try Event.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true }, self); + const submitter_html: ?*HtmlElement = if (submitter_) |s| s.is(HtmlElement) else null; + const submit_event = (try SubmitEvent.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true, .submitter = submitter_html }, self)).asEvent(); // so submit_event is still valid when we check _prevent_default submit_event.acquireRef(); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 50ce89dd..3893c839 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -850,6 +850,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/event/TextEvent.zig"), @import("../webapi/event/InputEvent.zig"), @import("../webapi/event/PromiseRejectionEvent.zig"), + @import("../webapi/event/SubmitEvent.zig"), @import("../webapi/MessageChannel.zig"), @import("../webapi/MessagePort.zig"), @import("../webapi/media/MediaError.zig"), diff --git a/src/browser/tests/element/html/form.html b/src/browser/tests/element/html/form.html index d3249856..9521e680 100644 --- a/src/browser/tests/element/html/form.html +++ b/src/browser/tests/element/html/form.html @@ -463,3 +463,44 @@ }); } + + +
+ +
+ + + + +
+ +
+ + diff --git a/src/browser/webapi/Event.zig b/src/browser/webapi/Event.zig index d70042f8..2264ba41 100644 --- a/src/browser/webapi/Event.zig +++ b/src/browser/webapi/Event.zig @@ -76,6 +76,7 @@ pub const Type = union(enum) { pop_state_event: *@import("event/PopStateEvent.zig"), ui_event: *@import("event/UIEvent.zig"), promise_rejection_event: *@import("event/PromiseRejectionEvent.zig"), + submit_event: *@import("event/SubmitEvent.zig"), }; pub const Options = struct { diff --git a/src/browser/webapi/event/SubmitEvent.zig b/src/browser/webapi/event/SubmitEvent.zig new file mode 100644 index 00000000..80cb4d36 --- /dev/null +++ b/src/browser/webapi/event/SubmitEvent.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 String = @import("../../../string.zig").String; + +const js = @import("../../js/js.zig"); +const Page = @import("../../Page.zig"); +const Session = @import("../../Session.zig"); +const Event = @import("../Event.zig"); +const HtmlElement = @import("../element/Html.zig"); +const Allocator = std.mem.Allocator; + +// https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent +const SubmitEvent = @This(); + +_proto: *Event, +_submitter: ?*HtmlElement, + +const SubmitEventOptions = struct { + submitter: ?*HtmlElement = null, +}; + +const Options = Event.inheritOptions(SubmitEvent, SubmitEventOptions); + +pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*SubmitEvent { + const arena = try page.getArena(.{ .debug = "SubmitEvent" }); + errdefer page.releaseArena(arena); + const type_string = try String.init(arena, typ, .{}); + return initWithTrusted(arena, type_string, opts_, false, page); +} + +pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*SubmitEvent { + const arena = try page.getArena(.{ .debug = "SubmitEvent.trusted" }); + errdefer page.releaseArena(arena); + return initWithTrusted(arena, typ, _opts, true, page); +} + +fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool, page: *Page) !*SubmitEvent { + const opts = _opts orelse Options{}; + + const event = try page._factory.event( + arena, + typ, + SubmitEvent{ + ._proto = undefined, + ._submitter = opts.submitter, + }, + ); + + Event.populatePrototypes(event, opts, trusted); + return event; +} + +pub fn deinit(self: *SubmitEvent, shutdown: bool, session: *Session) void { + self._proto.deinit(shutdown, session); +} + +pub fn asEvent(self: *SubmitEvent) *Event { + return self._proto; +} + +pub fn getSubmitter(self: *const SubmitEvent) ?*HtmlElement { + return self._submitter; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(SubmitEvent); + + pub const Meta = struct { + pub const name = "SubmitEvent"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + pub const weak = true; + pub const finalizer = bridge.finalizer(SubmitEvent.deinit); + }; + + pub const constructor = bridge.constructor(SubmitEvent.init, .{}); + pub const submitter = bridge.accessor(SubmitEvent.getSubmitter, null, .{}); +};