forms: extract Select.effectiveOption to centralize selectedness

Per review: collectForm walked the children once to look for a
non-disabled <option>, then Select.getValue walked them again to
emit the value. Extract the selectedness-candidate algorithm into
Select.effectiveOption() ?*Option so both call sites share it and
each runs a single iteration.

- Select.getValue is now a one-liner over effectiveOption.
- collectForm replaces the candidate-check + getValue pair with
  `select.effectiveOption() orelse continue; break :blk opt.getValue(frame);`.

Behavior is unchanged; the existing form_data.html fixtures
(selectWithoutOptions, selectMultipleWithoutOptions,
selectAllOptionsDisabled) still pass.
This commit is contained in:
Navid EMAD
2026-04-27 22:25:55 +02:00
parent 67239adb11
commit 6e65487767
2 changed files with 19 additions and 17 deletions

View File

@@ -44,8 +44,13 @@ pub fn asConstNode(self: *const Select) *const Node {
return self.asConstElement().asConstNode();
}
pub fn getValue(self: *Select, frame: *Frame) []const u8 {
// Return value of first selected option, or first option if none selected
// Resolves the option whose selectedness contributes to the select's value
// per HTML §form-elements§selectedness-setting-algorithm: an explicitly
// selected non-disabled option, falling back to the first non-disabled
// option in tree order. Returns null if there is no candidate (zero options
// or every option disabled), in which case the select has no selectedness
// and contributes no entry to a FormData set.
pub fn effectiveOption(self: *Select) ?*Option {
var first_option: ?*Option = null;
var iter = self.asNode().childrenIterator();
while (iter.next()) |child| {
@@ -55,14 +60,17 @@ pub fn getValue(self: *Select, frame: *Frame) []const u8 {
}
if (option.getSelected()) {
return option.getValue(frame);
return option;
}
if (first_option == null) {
first_option = option;
}
}
// No explicitly selected option, return first option's value
if (first_option) |opt| {
return first_option;
}
pub fn getValue(self: *Select, frame: *Frame) []const u8 {
if (self.effectiveOption()) |opt| {
return opt.getValue(frame);
}
return "";

View File

@@ -196,18 +196,12 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, frame: *F
if (element.is(Form.Select)) |select| {
if (select.getMultiple() == false) {
// Per the HTML spec, a single-select's selectedness comes
// from an explicitly-selected option, falling back to the
// first non-disabled option in tree order. With no options
// (or only disabled ones), nothing is selected and no
// entry is appended.
var children = select.asNode().childrenIterator();
const has_candidate = while (children.next()) |child| {
const option = child.is(Form.Select.Option) orelse continue;
if (!option.getDisabled()) break true;
} else false;
if (!has_candidate) continue;
break :blk select.getValue(frame);
// Per the HTML spec, a single-select with no selectedness
// candidate (zero options or every option disabled)
// contributes no entry. Otherwise emit the candidate's
// value.
const opt = select.effectiveOption() orelse continue;
break :blk opt.getValue(frame);
}
var options = try select.getSelectedOptions(frame);