From 989e2d03a24fe8097c655e73a2aae31b5e8807ad Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Tue, 12 May 2026 19:02:10 +0200 Subject: [PATCH] dom: implement HTMLDialogElement.{show, showModal, close} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HTMLDialogElement constructor was exposed with `open` / `returnValue` IDL accessors, but the three instance methods that drive the open/close state were missing. Per HTML ยง4.11.4 (The dialog element), `show()` sets the `open` attribute if absent; `showModal()` throws `InvalidStateError` when the dialog is already open and otherwise sets `open`; `close()` removes `open`, optionally updates `returnValue`, and fires a non- bubbling `close` event. The non-rendering steps (focus trap, backdrop, top-layer placement) are intentional no-ops here โ€” `[open]` reflecting through to selectors and the `close` event firing are what downstream CDP clients rely on. Closes #2434 --- src/browser/tests/element/html/dialog.html | 122 +++++++++++++++++++++ src/browser/webapi/element/html/Dialog.zig | 36 ++++++ 2 files changed, 158 insertions(+) diff --git a/src/browser/tests/element/html/dialog.html b/src/browser/tests/element/html/dialog.html index b4c26df5..e0e6fb45 100644 --- a/src/browser/tests/element/html/dialog.html +++ b/src/browser/tests/element/html/dialog.html @@ -76,3 +76,125 @@ testing.expectTrue(dialog instanceof HTMLDialogElement) } + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/element/html/Dialog.zig b/src/browser/webapi/element/html/Dialog.zig index c847af21..8bb3616f 100644 --- a/src/browser/webapi/element/html/Dialog.zig +++ b/src/browser/webapi/element/html/Dialog.zig @@ -1,6 +1,7 @@ const js = @import("../../../js/js.zig"); const Frame = @import("../../../Frame.zig"); +const Event = @import("../../Event.zig"); const Node = @import("../../Node.zig"); const Element = @import("../../Element.zig"); const HtmlElement = @import("../Html.zig"); @@ -39,6 +40,37 @@ pub fn setReturnValue(self: *Dialog, value: []const u8, frame: *Frame) !void { try self.asElement().setAttributeSafe(comptime .wrap("returnvalue"), .wrap(value), frame); } +/// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-show +/// If the open attribute is set, return; otherwise set it to the empty string. +/// Focus / inert / top-layer steps are no-ops here โ€” no rendering pipeline. +pub fn show(self: *Dialog, frame: *Frame) !void { + if (self.getOpen()) return; + try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), frame); +} + +/// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal +/// Throws InvalidStateError if [open] is already set. Sets [open] otherwise. +/// Focus trap, backdrop, and top-layer placement are no-ops โ€” Lightpanda has +/// no layout/compositor; [open] reflecting through to selectors is what +/// downstream consumers rely on. +pub fn showModal(self: *Dialog, frame: *Frame) !void { + if (self.getOpen()) return error.InvalidStateError; + try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), frame); +} + +/// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-close +/// If [open] is unset, return. Otherwise remove [open], optionally update +/// returnValue, and fire a `close` event (non-bubbling, non-cancelable). +pub fn close(self: *Dialog, return_value: ?[]const u8, frame: *Frame) !void { + if (!self.getOpen()) return; + try self.asElement().removeAttribute(comptime .wrap("open"), frame); + if (return_value) |v| { + try self.asElement().setAttributeSafe(comptime .wrap("returnvalue"), .wrap(v), frame); + } + const event = try Event.init("close", .{ .bubbles = false, .cancelable = false }, frame._page); + try frame._event_manager.dispatch(self.asElement().asEventTarget(), event); +} + pub const JsApi = struct { pub const bridge = js.Bridge(Dialog); @@ -50,6 +82,10 @@ pub const JsApi = struct { pub const open = bridge.accessor(Dialog.getOpen, Dialog.setOpen, .{}); pub const returnValue = bridge.accessor(Dialog.getReturnValue, Dialog.setReturnValue, .{}); + + pub const show = bridge.function(Dialog.show, .{}); + pub const showModal = bridge.function(Dialog.showModal, .{ .dom_exception = true }); + pub const close = bridge.function(Dialog.close, .{}); }; const testing = @import("../../../../testing.zig");