/* * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2022-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { GC_DEFINE_ALLOCATOR(HTMLOptionElement); static u64 m_next_selectedness_update_index = 1; HTMLOptionElement::HTMLOptionElement(DOM::Document& document, DOM::QualifiedName qualified_name) : HTMLElement(document, move(qualified_name)) { } HTMLOptionElement::~HTMLOptionElement() = default; void HTMLOptionElement::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLOptionElement); } void HTMLOptionElement::attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) { Base::attribute_changed(name, old_value, value, namespace_); if (name == HTML::AttributeNames::selected) { if (!value.has_value()) { // Whenever an option element's selected attribute is removed, if its dirtiness is false, its selectedness must be set to false. if (!m_dirty) set_selected_internal(false); } else { // Except where otherwise specified, when the element is created, its selectedness must be set to true // if the element has a selected attribute. Whenever an option element's selected attribute is added, // if its dirtiness is false, its selectedness must be set to true. if (!m_dirty) set_selected_internal(true); } } } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-selected void HTMLOptionElement::set_selected(bool selected) { // On setting, it must set the element's selectedness to the new value, set its dirtiness to true, and then cause the element to ask for a reset. set_selected_internal(selected); m_dirty = true; ask_for_a_reset(); } void HTMLOptionElement::set_selected_internal(bool selected) { if (m_selected != selected) invalidate_style(DOM::StyleInvalidationReason::HTMLOptionElementSelectedChange); m_selected = selected; if (selected) m_selectedness_update_index = m_next_selectedness_update_index++; } // 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. // ...or, if there is not, the value of the element's text IDL attribute. return attribute(HTML::AttributeNames::value).value_or(text()); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-value WebIDL::ExceptionOr HTMLOptionElement::set_value(String const& value) { return set_attribute(HTML::AttributeNames::value, value); } static void concatenate_descendants_text_content(DOM::Node const* node, StringBuilder& builder) { if (is(node) || 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); return IterationDecision::Continue; }); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-label String HTMLOptionElement::label() const { // The label IDL attribute, on getting, if there is a label content attribute, // must return that attribute's value; otherwise, it must return the element's label. if (auto label = attribute(HTML::AttributeNames::label); label.has_value()) return label.release_value(); return text(); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-label void HTMLOptionElement::set_label(String const& label) { MUST(set_attribute(HTML::AttributeNames::label, label)); // NOTE: This option's select element may need to show different contents now. if (selected()) { if (auto select_element = first_ancestor_of_type()) { select_element->update_inner_text_element({}); } } } // 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 IterationDecision::Continue; }); // Return the result of stripping and collapsing ASCII whitespace from the above concatenation. return MUST(Infra::strip_and_collapse_whitespace(builder.string_view())); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-text void HTMLOptionElement::set_text(String const& 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.ptr() == 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() { // If an option element in the list of options asks for a reset, then run that select element's selectedness setting algorithm. if (auto* select = first_ancestor_of_type()) { select->update_selectedness(); } } // https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled bool HTMLOptionElement::disabled() const { // An option element is disabled if its disabled attribute is present or if it is a child of an optgroup element whose disabled attribute is present. return has_attribute(AttributeNames::disabled) || (parent() && is(parent()) && static_cast(*parent()).has_attribute(AttributeNames::disabled)); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-form GC::Ptr HTMLOptionElement::form() const { // The form IDL attribute's behavior depends on whether the option element is in a select element or not. // If the option has a select element as its parent, or has an optgroup element as its parent and that optgroup element has a select element as its parent, // then the form IDL attribute must return the same value as the form IDL attribute on that select element. // Otherwise, it must return null. auto const* parent = parent_element(); if (is(parent)) parent = parent->parent_element(); if (is(parent)) { auto const* select_element = verify_cast(parent); return const_cast(select_element->form()); } return {}; } Optional HTMLOptionElement::default_role() const { // https://www.w3.org/TR/html-aria/#el-option // TODO: Only an option element that is in a list of options or that represents a suggestion in a datalist should return option return ARIA::Role::option; } void HTMLOptionElement::inserted() { Base::inserted(); set_selected_internal(selected()); // 1. The option HTML element insertion steps, given insertedNode, are: // If insertedNode's parent is a select element, // or insertedNode's parent is an optgroup element whose parent is a select element, // then run that select element's selectedness setting algorithm. if (is(*parent())) static_cast(*parent()).update_selectedness(); else if (is(parent()) && parent()->parent() && is(*parent()->parent())) static_cast(*parent()->parent()).update_selectedness(); } void HTMLOptionElement::removed_from(Node* old_parent) { Base::removed_from(old_parent); // The option HTML element removing steps, given removedNode and oldParent, are: // 1. If oldParent is a select element, or oldParent is an optgroup element whose parent is a select element, // then run that select element's selectedness setting algorithm. if (old_parent) { if (is(*old_parent)) static_cast(*old_parent).update_selectedness(); else if (is(*old_parent) && old_parent->parent_element() && is(old_parent->parent_element())) static_cast(*old_parent->parent_element()).update_selectedness(); } } }