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);