mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-03 03:13:05 -04:00
forms: close the ancestor dialog on method=dialog submission
`Form.normalizeMethod` already canonicalizes `method="dialog"`, but `Frame.submitForm` only branched on `"post"`, so a dialog submission fell through to the GET path: the document was reloaded, the dialog kept its `open` attribute, `returnValue` was never set, and no `close` event fired. A promise awaiting `close` — the idiomatic way to await a confirm dialog — hung forever while the page reloaded underneath it. Add the branch the HTML form-submission algorithm calls for: walk up from the form to the nearest ancestor `<dialog>` and close it with the submitter's value through the existing `Dialog.close()`, then return without scheduling a navigation. With no ancestor dialog the submission is dropped, which is also what the spec asks for — and still not a navigation. Completes the `<dialog>` support started in #2435, which implemented `show`/`showModal`/`close`/`returnValue` but left the form-submission integration that drives them unwired. Closes #3053
This commit is contained in:
@@ -3404,6 +3404,31 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
|
||||
const method = Element.Html.Form.normalizeMethod(method_attr, "get");
|
||||
const is_post = std.mem.eql(u8, method, "post");
|
||||
|
||||
// Per the HTML form-submission algorithm, the dialog method closes the
|
||||
// form's nearest ancestor dialog with the submitter's value and performs no
|
||||
// navigation. Falling through here would submit the form as a GET, which
|
||||
// both reloads the page and leaves the dialog open forever.
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
|
||||
if (std.mem.eql(u8, method, "dialog")) {
|
||||
var ancestor: ?*Element = form_element;
|
||||
while (ancestor) |el| : (ancestor = el.parentElement()) {
|
||||
const dialog = el.is(Element.Html.Dialog) orelse continue;
|
||||
// A submit button always has a value (empty when the attribute is
|
||||
// absent); with no submitter at all the dialog's existing
|
||||
// returnValue is left untouched.
|
||||
const result: ?[]const u8 = if (submit_button) |s|
|
||||
s.getAttributeSafe(comptime .wrap("value")) orelse ""
|
||||
else
|
||||
null;
|
||||
try dialog.close(result, self);
|
||||
break;
|
||||
}
|
||||
// Nothing is navigated, so the arena reserved for the request body goes
|
||||
// unused — the errdefer above only covers the error path.
|
||||
self._session.releaseArena(arena);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get charset from accept-charset attribute or fall back to document charset
|
||||
const charset: []const u8 = blk: {
|
||||
if (form_element.getAttributeSafe(.wrap("accept-charset"))) |ac| {
|
||||
|
||||
@@ -198,3 +198,99 @@
|
||||
testing.expectEqual(0, fired)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="dialog_method_closes_with_submitter_value">
|
||||
{
|
||||
document.body.insertAdjacentHTML('beforeend',
|
||||
'<dialog id="d_submit"><form method="dialog">' +
|
||||
'<button type="submit" value="confirm" id="b_submit">ok</button>' +
|
||||
'</form></dialog>')
|
||||
|
||||
const dialog = $('#d_submit')
|
||||
dialog.showModal()
|
||||
|
||||
let fired = 0
|
||||
dialog.addEventListener('close', () => fired++)
|
||||
|
||||
$('#b_submit').click()
|
||||
|
||||
testing.expectEqual(1, fired)
|
||||
testing.expectEqual(false, dialog.open)
|
||||
testing.expectEqual('confirm', dialog.returnValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="dialog_method_uses_empty_value_when_submitter_has_none">
|
||||
{
|
||||
document.body.insertAdjacentHTML('beforeend',
|
||||
'<dialog id="d_novalue"><form method="dialog">' +
|
||||
'<button type="submit" id="b_novalue">ok</button>' +
|
||||
'</form></dialog>')
|
||||
|
||||
const dialog = $('#d_novalue')
|
||||
dialog.returnValue = 'preset'
|
||||
dialog.showModal()
|
||||
|
||||
$('#b_novalue').click()
|
||||
|
||||
testing.expectEqual(false, dialog.open)
|
||||
testing.expectEqual('', dialog.returnValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="dialog_method_closes_nearest_ancestor_dialog">
|
||||
{
|
||||
document.body.insertAdjacentHTML('beforeend',
|
||||
'<dialog id="d_outer"><dialog id="d_inner"><form method="dialog">' +
|
||||
'<button type="submit" value="inner" id="b_nested">ok</button>' +
|
||||
'</form></dialog></dialog>')
|
||||
|
||||
const outer = $('#d_outer')
|
||||
const inner = $('#d_inner')
|
||||
outer.show()
|
||||
inner.show()
|
||||
|
||||
$('#b_nested').click()
|
||||
|
||||
testing.expectEqual(false, inner.open)
|
||||
testing.expectEqual('inner', inner.returnValue)
|
||||
testing.expectEqual(true, outer.open)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="dialog_method_is_inert_without_ancestor_dialog">
|
||||
{
|
||||
// No ancestor dialog: the submission is dropped. It must not fall through
|
||||
// to a GET submission either, so the form's own submit handler is the only
|
||||
// thing that runs.
|
||||
document.body.insertAdjacentHTML('beforeend',
|
||||
'<form method="dialog" id="f_orphan">' +
|
||||
'<button type="submit" value="x" id="b_orphan">ok</button>' +
|
||||
'</form>')
|
||||
|
||||
let submits = 0
|
||||
$('#f_orphan').addEventListener('submit', () => submits++)
|
||||
|
||||
$('#b_orphan').click()
|
||||
testing.expectEqual(1, submits)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="dialog_method_honors_formmethod_on_submitter">
|
||||
{
|
||||
// formmethod on the submit button overrides the form's method, so a GET
|
||||
// form can still close its dialog.
|
||||
document.body.insertAdjacentHTML('beforeend',
|
||||
'<dialog id="d_formmethod"><form method="get">' +
|
||||
'<button type="submit" formmethod="dialog" value="via-formmethod" id="b_formmethod">ok</button>' +
|
||||
'</form></dialog>')
|
||||
|
||||
const dialog = $('#d_formmethod')
|
||||
dialog.showModal()
|
||||
|
||||
$('#b_formmethod').click()
|
||||
|
||||
testing.expectEqual(false, dialog.open)
|
||||
testing.expectEqual('via-formmethod', dialog.returnValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user