diff --git a/src/browser/webapi/element/html/Select.zig b/src/browser/webapi/element/html/Select.zig index 5ad09e39..a840c533 100644 --- a/src/browser/webapi/element/html/Select.zig +++ b/src/browser/webapi/element/html/Select.zig @@ -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 ""; diff --git a/src/browser/webapi/net/FormData.zig b/src/browser/webapi/net/FormData.zig index 0258dd8e..cd8584da 100644 --- a/src/browser/webapi/net/FormData.zig +++ b/src/browser/webapi/net/FormData.zig @@ -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);