diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp
index 5a35687dcf..8f4fed8b60 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp
@@ -5,7 +5,13 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include
+#include
+#include
#include
+#include
+#include
+#include
namespace Web::HTML {
@@ -49,6 +55,99 @@ void HTMLOptionElement::set_selected(bool selected)
ask_for_a_reset();
}
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-value
+String HTMLOptionElement::value() const
+{
+ // The value of an option element is the value of the value content attribute, if there is one.
+ if (auto value_attr = get_attribute(HTML::AttributeNames::value); !value_attr.is_null())
+ return value_attr;
+
+ // ...or, if there is not, the value of the element's text IDL attribute.
+ return text();
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-value
+void HTMLOptionElement::set_value(String value)
+{
+ set_attribute(HTML::AttributeNames::value, value);
+}
+
+// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
+static String strip_and_collapse_whitespace(StringView string)
+{
+ // Replace any sequence of one or more consecutive code points that are ASCII whitespace in the string with a single U+0020 SPACE code point.
+ StringBuilder builder;
+ for (size_t i = 0; i < string.length(); ++i) {
+ if (isspace(string[i])) {
+ builder.append(' ');
+ while (i < string.length()) {
+ if (isspace(string[i])) {
+ ++i;
+ continue;
+ }
+ builder.append(string[i]);
+ break;
+ }
+ continue;
+ }
+ builder.append(string[i]);
+ }
+
+ // ...and then remove any leading and trailing ASCII whitespace from that string.
+ return builder.to_string().trim_whitespace();
+}
+
+static void concatenate_descendants_text_content(DOM::Node const* node, StringBuilder& builder)
+{
+ // FIXME: SVGScriptElement should also be skipped, but it doesn't exist yet.
+ if (is(node))
+ return;
+ if (is(node))
+ builder.append(verify_cast(node)->data());
+ node->for_each_child([&](auto const& node) {
+ concatenate_descendants_text_content(&node, builder);
+ });
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-text
+String HTMLOptionElement::text() const
+{
+ StringBuilder builder;
+
+ // Concatenation of data of all the Text node descendants of the option element, in tree order,
+ // excluding any that are descendants of descendants of the option element that are themselves
+ // script or SVG script elements.
+ for_each_child([&](auto const& node) {
+ concatenate_descendants_text_content(&node, builder);
+ });
+
+ // Return the result of stripping and collapsing ASCII whitespace from the above concatenation.
+ return strip_and_collapse_whitespace(builder.to_string());
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-text
+void HTMLOptionElement::set_text(String text)
+{
+ string_replace_all(text);
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-index
+int HTMLOptionElement::index() const
+{
+ // An option element's index is the number of option elements that are in the same list of options but that come before it in tree order.
+ if (auto select_element = first_ancestor_of_type()) {
+ int index = 0;
+ for (auto const& option_element : select_element->list_of_options()) {
+ if (&option_element == this)
+ return index;
+ ++index;
+ }
+ }
+
+ // If the option element is not in a list of options, then the option element's index is zero.
+ return 0;
+}
+
// https://html.spec.whatwg.org/multipage/form-elements.html#ask-for-a-reset
void HTMLOptionElement::ask_for_a_reset()
{
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h
index d8ce1708f7..8b83003ced 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h
@@ -21,6 +21,14 @@ public:
bool selected() const { return m_selected; }
void set_selected(bool);
+ String value() const;
+ void set_value(String);
+
+ String text() const;
+ void set_text(String);
+
+ int index() const;
+
private:
friend class Bindings::OptionConstructor;
friend class HTMLSelectElement;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl
index edc12f5111..5a2a1f5395 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl
@@ -4,7 +4,10 @@ interface HTMLOptionElement : HTMLElement {
[Reflect] attribute boolean disabled;
[Reflect=selected] attribute boolean defaultSelected;
-
attribute boolean selected;
+ [CEReactions] attribute DOMString value;
+
+ [CEReactions] attribute DOMString text;
+ readonly attribute long index;
};