From 77bbbf5e843a6ab73d4d4913278809fad99b6bed Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Fri, 24 Jul 2026 21:23:52 +0200 Subject: [PATCH] forms: close the ancestor dialog on method=dialog submission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 `` 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 `` support started in #2435, which implemented `show`/`showModal`/`close`/`returnValue` but left the form-submission integration that drives them unwired. Closes #3053 --- src/browser/Frame.zig | 25 ++++++ src/browser/tests/element/html/dialog.html | 96 ++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 7e13f3db3..abd9c6aaf 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -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| { diff --git a/src/browser/tests/element/html/dialog.html b/src/browser/tests/element/html/dialog.html index e0e6fb45c..59aecd4db 100644 --- a/src/browser/tests/element/html/dialog.html +++ b/src/browser/tests/element/html/dialog.html @@ -198,3 +198,99 @@ testing.expectEqual(0, fired) } + + + + + + + + + +