Merge pull request #2279 from navidemad/fix-a20-form-attrs-on-submitter

forms: honor formaction / formmethod / formenctype on submit button
This commit is contained in:
Karl Seguin
2026-04-28 09:49:48 +08:00
committed by GitHub
2 changed files with 92 additions and 3 deletions

View File

@@ -3753,7 +3753,16 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
const arena = try self._session.getArena(.medium, "submitForm");
errdefer self._session.releaseArena(arena);
const enctype = form_element.getAttributeSafe(comptime .wrap("enctype"));
// Per HTML spec form-submission algorithm, when the submitter is a submit
// button, its formaction/formmethod/formenctype attributes override the
// form's corresponding attributes (matching how formtarget is honored above).
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit
const enctype = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formenctype"))) |fe| break :blk fe;
}
break :blk form_element.getAttributeSafe(comptime .wrap("enctype"));
};
// Get charset from accept-charset attribute or fall back to document charset
const charset: []const u8 = blk: {
@@ -3770,8 +3779,18 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
var buf = std.Io.Writer.Allocating.init(arena);
try form_data.write(.{ .enctype = enctype, .charset = charset, .allocator = arena }, &buf.writer);
const method = form_element.getAttributeSafe(comptime .wrap("method")) orelse "";
var action = form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
const method = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formmethod"))) |fm| break :blk fm;
}
break :blk form_element.getAttributeSafe(comptime .wrap("method")) orelse "";
};
var action = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formaction"))) |fa| break :blk fa;
}
break :blk form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
};
var opts = NavigateOpts{
.reason = .form,

View File

@@ -56,3 +56,73 @@
});
}
</script>
<!-- Per HTML spec, formaction on the submit button overrides the form's action.
https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit -->
<iframe name=frame4 id=f4></iframe>
<form target="frame4" action="support/should-not-load.html">
<input type=submit id=submit_formaction formaction="support/page.html">
</form>
<script id=formaction type=module>
{
const state = await testing.async();
$('#submit_formaction').click();
$('#f4').onload = () => {
state.resolve();
};
await state.done(() => {
testing.expectEqual('a-page\n', $('#f4').contentDocument.body.textContent);
});
}
</script>
<!-- Per HTML spec, formmethod on the submit button overrides the form's method.
A form with method=GET would append the FormData as ?x=probe to the action;
formmethod=POST should suppress that and send the values in the request body.
We verify the override by asserting the loaded iframe URL has no query string. -->
<iframe name=frame5 id=f5></iframe>
<form target="frame5" method="GET" action="support/page.html">
<input name="x" value="probe">
<input type=submit id=submit_formmethod formmethod="POST">
</form>
<script id=formmethod type=module>
{
const state = await testing.async();
$('#submit_formmethod').click();
$('#f5').onload = () => {
state.resolve();
};
await state.done(() => {
// formmethod=POST overrode form's GET, so the FormData entry was NOT
// appended to the action URL as a query string.
testing.expectEqual(false, $('#f5').contentDocument.URL.includes('?'));
testing.expectEqual('a-page\n', $('#f5').contentDocument.body.textContent);
});
}
</script>
<!-- Combined: formaction + formmethod on the same submit button. -->
<iframe name=frame6 id=f6></iframe>
<form target="frame6" method="GET" action="support/should-not-load.html">
<input name="x" value="probe">
<input type=submit id=submit_form_both formaction="support/page.html" formmethod="POST">
</form>
<script id=formaction_and_formmethod type=module>
{
const state = await testing.async();
$('#submit_form_both').click();
$('#f6').onload = () => {
state.resolve();
};
await state.done(() => {
testing.expectEqual(false, $('#f6').contentDocument.URL.includes('?'));
testing.expectEqual('a-page\n', $('#f6').contentDocument.body.textContent);
});
}
</script>