From 359eb13f0e59027810c60defea593353b6f55985 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Thu, 1 Oct 2026 23:35:12 +0200 Subject: [PATCH 1/2] Fix button row forms crossing table rows This PR fixes #2853, where buttons made by `ui_buttons_row` do nothing in strict browsers like Pale Moon. Each form was placed between table rows, which HTML doesn't allow, so the browser closed it right away and left the button outside it. Now the form sits inside the button cell, and any fields in the description cell are linked to it with the HTML5 form attribute. The page looks exactly the same. Fixes https://github.com/webmin/webmin/issues/2853 --- ui-lib.pl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ui-lib.pl b/ui-lib.pl index b43af3629..7e2aa198f 100755 --- a/ui-lib.pl +++ b/ui-lib.pl @@ -1,7 +1,7 @@ use vars qw($theme_no_table $ui_radio_selector_donejs $module_name $ui_multi_select_donejs, $ui_formcount, $ui_form_end_side_by_side_donecss, - $ui_form_grouped_buttons_donecss); + $ui_form_grouped_buttons_donecss, $ui_buttons_formcount); =head1 ui-lib.pl @@ -1856,7 +1856,7 @@ inputs. The parameters are : =item button-label - Text to appear on the button. -=item description - Text to appear next to the button, describing in more detail what it does. +=item description - Text to appear next to the button, describing in more detail what it does. Inputs in it are linked to the button's form by the HTML5 C
attribute. =item hiddens - HTML for hidden fields to include in the form this function generates. @@ -1880,19 +1880,27 @@ $postmethod ||= 'post'; if (ref($hiddens)) { $hiddens = join("\n", map { &ui_hidden(@$_) } @$hiddens); } -return "\n". - $hiddens. - " ". + +# A form cannot wrap table rows, so it lives in the button cell. Inputs in +# the description cell are linked to it by the HTML5 form attribute +my $formid; +if (!$singlecell && $desc =~ /<(input|select|textarea|button)\b/i) { + $formid = "ui_buttons_form_".(++$ui_buttons_formcount); + $desc =~ s/<(input|select|textarea|button)\b(?![^>]*\sform\s*=)/<$1 form='$formid'/gi; + } +return " ". "". + "\n". + $hiddens. ($before ? $before." " : ""). &ui_submit($label, '', $disabled). - ($after ? " ".$after : "")."\n". + ($after ? " ".$after : "")."
\n". ($singlecell ? "" : "". $desc."\n"). - "\n". - "\n"; + "\n"; } =head2 ui_buttons_hr([title]) From 875c973658f18bcfd3b046d2c2bbf1c857fae04c Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Fri, 2 Oct 2026 05:26:15 +0200 Subject: [PATCH 2/2] Change button rows to use one form and table per row --- gray-theme/unauthenticated/gray-theme.css | 9 ++++++- ui-lib.pl | 29 +++++++++++------------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/gray-theme/unauthenticated/gray-theme.css b/gray-theme/unauthenticated/gray-theme.css index 9cf39151f..7123e5c7c 100644 --- a/gray-theme/unauthenticated/gray-theme.css +++ b/gray-theme/unauthenticated/gray-theme.css @@ -613,7 +613,14 @@ tr.ui_buttons_row > td { border-top: 1px solid var(--border-color-lighter); vertical-align: middle; } -tr.ui_buttons_row:first-child > td { border-top: 0; } +/* Each row is a form with a table of its own, as a form cannot wrap table + * rows. Flattening the wrappers lets all rows share the outer columns */ +tr.ui_buttons_form_row, +tr.ui_buttons_form_row > td, +form.ui_buttons_form, +form.ui_buttons_form > table, +form.ui_buttons_form > table > tbody { display: contents; } +tr.ui_buttons_form_row:first-child tr.ui_buttons_row > td { border-top: 0; } td.ui_buttons_label { width: 1%; padding-right: 16px; diff --git a/ui-lib.pl b/ui-lib.pl index 7e2aa198f..4594ee88f 100755 --- a/ui-lib.pl +++ b/ui-lib.pl @@ -1,7 +1,7 @@ use vars qw($theme_no_table $ui_radio_selector_donejs $module_name $ui_multi_select_donejs, $ui_formcount, $ui_form_end_side_by_side_donecss, - $ui_form_grouped_buttons_donecss, $ui_buttons_formcount); + $ui_form_grouped_buttons_donecss); =head1 ui-lib.pl @@ -1856,7 +1856,7 @@ inputs. The parameters are : =item button-label - Text to appear on the button. -=item description - Text to appear next to the button, describing in more detail what it does. Inputs in it are linked to the button's form by the HTML5 C
attribute. +=item description - Text to appear next to the button, describing in more detail what it does. =item hiddens - HTML for hidden fields to include in the form this function generates. @@ -1881,26 +1881,25 @@ if (ref($hiddens)) { $hiddens = join("\n", map { &ui_hidden(@$_) } @$hiddens); } -# A form cannot wrap table rows, so it lives in the button cell. Inputs in -# the description cell are linked to it by the HTML5 form attribute -my $formid; -if (!$singlecell && $desc =~ /<(input|select|textarea|button)\b/i) { - $formid = "ui_buttons_form_".(++$ui_buttons_formcount); - $desc =~ s/<(input|select|textarea|button)\b(?![^>]*\sform\s*=)/<$1 form='$formid'/gi; - } -return " ". +# A form cannot wrap table rows, so each row is a full-width cell holding +# the form, which wraps a table of its own +return "\n". + "\n". + $hiddens. + "\n". + " ". "\n". + ($after ? " ".$after : "")."\n". ($singlecell ? "" : "\n"). - "\n"; + "\n". + "
". - "\n". - $hiddens. ($before ? $before." " : ""). &ui_submit($label, '', $disabled). - ($after ? " ".$after : "")."". $desc."
\n". + "\n". + "\n"; } =head2 ui_buttons_hr([title])