| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * Copyright (C) 2004-2026 Apple Inc. All rights reserved. |
| * Copyright (C) 2010-2017 Google Inc. All rights reserved. |
| * Copyright (C) 2011 Motorola Mobility, Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "HTMLOptionElement.h" |
| |
| #include "AXObjectCache.h" |
| #include "ContainerNodeInlines.h" |
| #include "Document.h" |
| #include "ElementAncestorIteratorInlines.h" |
| #include "EventNames.h" |
| #include "HTMLDataListElement.h" |
| #include "HTMLHRElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLOptGroupElement.h" |
| #include "HTMLSelectElement.h" |
| #include "HTMLSelectedContentElement.h" |
| #include "HTMLSlotElement.h" |
| #include "HTMLSpanElement.h" |
| #include "KeyboardEvent.h" |
| #include "MouseEvent.h" |
| #include "NodeName.h" |
| #include "NodeRenderStyle.h" |
| #include "NodeTraversal.h" |
| #include "PseudoClassChangeInvalidation.h" |
| #include "RenderTheme.h" |
| #include "ScriptDisallowedScope.h" |
| #include "ScriptElement.h" |
| #include "SelectPopoverElement.h" |
| #include "StyleComputedStyle+GettersInlines.h" |
| #include "StyleResolver.h" |
| #include "Text.h" |
| #include <wtf/Ref.h> |
| #include <wtf/TZoneMallocInlines.h> |
| #include <wtf/text/MakeString.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(HTMLOptionElement); |
| |
| using namespace HTMLNames; |
| |
| HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document& document) |
| : HTMLElement(tagName, document, TypeFlag::HasCustomStyleResolveCallbacks) |
| { |
| ASSERT(hasTagName(optionTag)); |
| } |
| |
| Ref<HTMLOptionElement> HTMLOptionElement::create(Document& document) |
| { |
| return adoptRef(*new HTMLOptionElement(optionTag, document)); |
| } |
| |
| Ref<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document& document) |
| { |
| return adoptRef(*new HTMLOptionElement(tagName, document)); |
| } |
| |
| ExceptionOr<Ref<HTMLOptionElement>> HTMLOptionElement::createForLegacyFactoryFunction(Document& document, String&& text, const AtomString& value, bool defaultSelected, bool selected) |
| { |
| Ref element = create(document); |
| |
| if (!text.isEmpty()) { |
| auto appendResult = element->appendChild(Text::create(document, WTF::move(text))); |
| if (appendResult.hasException()) |
| return appendResult.releaseException(); |
| } |
| |
| if (!value.isNull()) |
| element->setAttributeWithoutSynchronization(valueAttr, value); |
| if (defaultSelected) |
| element->setAttributeWithoutSynchronization(selectedAttr, emptyAtom()); |
| element->setSelected(selected); |
| |
| return element; |
| } |
| |
| void HTMLOptionElement::didAddUserAgentShadowRoot(ShadowRoot& root) |
| { |
| Ref document = this->document(); |
| ScriptDisallowedScope::EventAllowedScope rootScope { root }; |
| |
| Ref labelContainer = HTMLSpanElement::create(document); |
| root.appendChild(labelContainer); |
| m_labelContainer = WTF::move(labelContainer); |
| |
| Ref slot = HTMLSlotElement::create(slotTag, document); |
| root.appendChild(slot); |
| m_slot = WTF::move(slot); |
| } |
| |
| void HTMLOptionElement::invalidateShadowTree() |
| { |
| if (!document().settings().htmlEnhancedSelectEnabled()) |
| return; |
| |
| if (m_shadowTreeNeedsUpdate) |
| return; |
| |
| m_shadowTreeNeedsUpdate = true; |
| if (isConnected()) |
| protect(document())->addElementWithPendingUserAgentShadowTreeUpdate(*this); |
| } |
| |
| void HTMLOptionElement::updateUserAgentShadowTree() |
| { |
| if (!m_shadowTreeNeedsUpdate) |
| return; |
| |
| m_shadowTreeNeedsUpdate = false; |
| protect(document())->removeElementWithPendingUserAgentShadowTreeUpdate(*this); |
| |
| if (!m_ownerSelect && !userAgentShadowRoot()) |
| return; |
| |
| if (!userAgentShadowRoot()) { |
| if (attributeWithoutSynchronization(labelAttr).isNull()) |
| return; |
| ensureUserAgentShadowRoot(); |
| } |
| |
| Ref labelContainer = *m_labelContainer; |
| Ref slot = *m_slot; |
| auto labelValue = attributeWithoutSynchronization(labelAttr); |
| |
| ScriptDisallowedScope::EventAllowedScope labelContainerScope { labelContainer }; |
| ScriptDisallowedScope::EventAllowedScope slotScope { slot }; |
| |
| labelContainer->setTextContent(String { labelValue }); |
| if (m_ownerSelect && !labelValue.isNull()) { |
| labelContainer->setInlineStyleProperty(CSSPropertyDisplay, CSSValueInline); |
| slot->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone); |
| } else { |
| labelContainer->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone); |
| slot->setInlineStyleProperty(CSSPropertyDisplay, CSSValueContents); |
| } |
| } |
| |
| auto HTMLOptionElement::insertionSteps(InsertionType insertionType, ContainerNode& parentOfInsertedTree) -> NeedsPostConnectionSteps |
| { |
| auto result = HTMLElement::insertionSteps(insertionType, parentOfInsertedTree); |
| |
| if (document().settings().htmlEnhancedSelectParsingEnabled() && !m_ownerSelect) { |
| if (RefPtr select = HTMLSelectElement::findOwnerSelect(parentNode(), HTMLSelectElement::ExcludeOptGroup::No)) { |
| m_ownerSelect = select.get(); |
| select->setRecalcListItems(); |
| // If this non-selected option is the first non-disabled option in a |
| // single-select that has no explicitly selected option, select it by |
| // default. This maintains m_isSelected incrementally so that |
| // finishParsingChildren() can use selectedWithoutUpdate() (O(1)) |
| // instead of selected() which triggers O(n) recalcListItems(). |
| // Only do this during parsing — for API insertions, the existing |
| // childrenChanged → optionToSelectFromChildChangeScope path handles it. |
| if (!select->isFinishedParsingChildren() && !selectedWithoutUpdate() && !m_disabled) |
| select->selectDefaultOptionIfNeeded(*this); |
| } |
| } |
| |
| if (insertionType.connectedToDocument) { |
| if (RefPtr select = ownerSelectElement()) |
| select->invalidateButtonText(); |
| if (m_shadowTreeNeedsUpdate) |
| protect(document())->addElementWithPendingUserAgentShadowTreeUpdate(*this); |
| } |
| |
| return result; |
| } |
| |
| void HTMLOptionElement::removingSteps(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| HTMLElement::removingSteps(removalType, oldParentOfRemovedTree); |
| |
| if (removalType.disconnectedFromDocument && m_shadowTreeNeedsUpdate) |
| protect(document())->removeElementWithPendingUserAgentShadowTreeUpdate(*this); |
| |
| if (!document().settings().htmlEnhancedSelectParsingEnabled() || !m_ownerSelect) |
| return; |
| |
| if (auto* select = HTMLSelectElement::findOwnerSelect(parentNode(), HTMLSelectElement::ExcludeOptGroup::No)) { |
| ASSERT_UNUSED(select, select == m_ownerSelect.get()); |
| return; |
| } |
| |
| if (RefPtr select = std::exchange(m_ownerSelect, nullptr).get()) { |
| select->setRecalcListItems(); |
| select->invalidateButtonText(); |
| invalidateShadowTree(); |
| } |
| } |
| |
| void HTMLOptionElement::movingSteps(IsSubtreeRoot isSubtreeRoot, ContainerNode& oldParent) |
| { |
| HTMLElement::movingSteps(isSubtreeRoot, oldParent); |
| |
| if (isSubtreeRoot == IsSubtreeRoot::No) |
| return; |
| |
| if (!document().settings().htmlEnhancedSelectParsingEnabled()) |
| return; |
| |
| RefPtr oldSelect = m_ownerSelect; |
| RefPtr newSelect = HTMLSelectElement::findOwnerSelect(parentNode(), HTMLSelectElement::ExcludeOptGroup::No); |
| if (oldSelect == newSelect) { |
| if (newSelect) { |
| newSelect->setRecalcListItems(); |
| newSelect->invalidateButtonText(); |
| } |
| return; |
| } |
| |
| m_ownerSelect = newSelect.get(); |
| |
| if (oldSelect) { |
| oldSelect->setRecalcListItems(); |
| oldSelect->invalidateButtonText(); |
| } |
| |
| if (newSelect) { |
| newSelect->setRecalcListItems(); |
| newSelect->invalidateButtonText(); |
| } |
| |
| invalidateShadowTree(); |
| } |
| |
| void HTMLOptionElement::finishParsingChildren() |
| { |
| if (!document().settings().htmlEnhancedSelectEnabled()) |
| return; |
| |
| if (document().settings().mutationEventsEnabled()) |
| return; |
| |
| ASSERT(document().settings().htmlEnhancedSelectParsingEnabled()); |
| |
| if (m_disabled) |
| return; |
| |
| RefPtr select = m_ownerSelect; |
| if (!select) |
| return; |
| |
| // When the owning <select> is still being parsed, use selectedWithoutUpdate() |
| // instead of selected() to avoid triggering recalcListItems() for each |
| // option, which would be O(n²). The selection state (m_isSelected) is |
| // maintained incrementally at insertion time — see insertionSteps() and |
| // optionToSelectFromChildChangeScope(). |
| bool isSelected = select->isFinishedParsingChildren() ? selected() : selectedWithoutUpdate(); |
| if (!isSelected) |
| return; |
| |
| // Pass `this` to avoid updateSelectedContent() calling listItems() to find |
| // the selected option, which would also trigger recalcListItems(). |
| select->updateSelectedContent(this); |
| } |
| |
| bool HTMLOptionElement::supportsFocus() const |
| { |
| return HTMLElement::supportsFocus() || belongsToBaseAppearancePicker(); |
| } |
| |
| bool HTMLOptionElement::isFocusable() const |
| { |
| RefPtr select = ownerSelectElement(); |
| if (select && select->usesMenuList() && !select->usesBaseAppearancePicker()) |
| return false; |
| return HTMLElement::isFocusable(); |
| } |
| |
| String HTMLOptionElement::text() const |
| { |
| String text = collectOptionInnerText(); |
| |
| // FIXME: Is displayStringModifiedByEncoding helpful here? |
| // If it's correct here, then isn't it needed in the value and label functions too? |
| return protect(document())->displayStringModifiedByEncoding(text).trim(isASCIIWhitespace).simplifyWhiteSpace(isASCIIWhitespace); |
| } |
| |
| void HTMLOptionElement::setText(String&& text) |
| { |
| Ref protectedThis { *this }; |
| |
| // Changing the text causes a recalc of a select's items, which will reset the selected |
| // index to the first item if the select is single selection with a menu list. We attempt to |
| // preserve the selected item. |
| RefPtr select = ownerSelectElement(); |
| bool selectIsMenuList = select && select->usesMenuListDeprecated(); |
| int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1; |
| |
| setTextContent(WTF::move(text)); |
| |
| if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex) |
| select->setSelectedIndex(oldSelectedIndex); |
| } |
| |
| bool HTMLOptionElement::accessKeyAction(bool) |
| { |
| RefPtr select = ownerSelectElement(); |
| if (!select) |
| return false; |
| |
| if (select->usesBaseAppearancePicker()) { |
| select->optionSelectedByUser(index(), true); |
| select->hidePickerPopoverElement(); |
| } else |
| select->accessKeySetSelectedIndex(index()); |
| return true; |
| } |
| |
| void HTMLOptionElement::defaultEventHandler(Event& event) |
| { |
| if (!event.isTrusted()) |
| return HTMLElement::defaultEventHandler(event); |
| |
| RefPtr select = ownerSelectElement(); |
| if (!select || !select->document().settings().htmlEnhancedSelectEnabled() || !select->usesBaseAppearancePicker()) |
| return HTMLElement::defaultEventHandler(event); |
| |
| auto& eventNames = WebCore::eventNames(); |
| |
| if (event.type() == eventNames.keydownEvent) { |
| RefPtr keyboardEvent = dynamicDowncast<KeyboardEvent>(event); |
| if (!keyboardEvent) |
| return HTMLElement::defaultEventHandler(event); |
| |
| const String& keyIdentifier = keyboardEvent->keyIdentifier(); |
| |
| // [Shift+]Tab closes the picker; fall through to move focus. |
| if (keyIdentifier == "U+0009"_s) { |
| select->hidePickerPopoverElement(); |
| return HTMLElement::defaultEventHandler(event); |
| } |
| |
| int currentIndex = select->optionToListIndex(index()); |
| int listIndex = select->computeNavigationIndex(keyIdentifier, currentIndex, select->pickerNavigationKeyIdentifiers()); |
| if (listIndex >= 0) { |
| auto scrollMode = HTMLSelectElement::PickerScrollMode::Nearest; |
| if (keyIdentifier == "PageDown"_s) |
| scrollMode = HTMLSelectElement::PickerScrollMode::AlignBottom; |
| else if (keyIdentifier == "PageUp"_s) |
| scrollMode = HTMLSelectElement::PickerScrollMode::AlignTop; |
| select->focusOptionAtIndex(listIndex, std::nullopt, scrollMode); |
| keyboardEvent->setDefaultHandled(); |
| return; |
| } |
| } |
| |
| if (event.type() == eventNames.keypressEvent) { |
| RefPtr keyboardEvent = dynamicDowncast<KeyboardEvent>(event); |
| if (!keyboardEvent) |
| return HTMLElement::defaultEventHandler(event); |
| |
| int keyCode = keyboardEvent->keyCode(); |
| if (keyCode == '\r' || keyCode == ' ') { |
| select->optionSelectedByUser(index(), true); |
| select->hidePickerPopoverElement(); |
| keyboardEvent->setDefaultHandled(); |
| return; |
| } |
| |
| if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && u_isprint(keyboardEvent->charCode())) { |
| int listIndex = select->typeAheadMatchIndex(*keyboardEvent); |
| if (listIndex >= 0) |
| select->focusOptionAtIndex(listIndex); |
| keyboardEvent->setDefaultHandled(); |
| return; |
| } |
| } |
| |
| if (RefPtr mouseEvent = dynamicDowncast<MouseEvent>(event); mouseEvent && event.type() == eventNames.mousedownEvent && mouseEvent->button() == MouseButton::Left) { |
| select->optionSelectedByUser(index(), true); |
| select->hidePickerPopoverElement(); |
| event.setDefaultHandled(); |
| return; |
| } |
| |
| HTMLElement::defaultEventHandler(event); |
| } |
| |
| HTMLFormElement* HTMLOptionElement::form() const |
| { |
| if (auto* selectElement = ownerSelectElement()) |
| return selectElement->form(); |
| return nullptr; |
| } |
| |
| RefPtr<HTMLFormElement> HTMLOptionElement::formForBindings() const |
| { |
| // FIXME: The downcast should be unnecessary, but the WPT was written before https://github.com/WICG/webcomponents/issues/1072 was resolved. Update once the WPT has been updated. |
| return dynamicDowncast<HTMLFormElement>(retargetReferenceTargetForBindings(form())); |
| } |
| |
| int HTMLOptionElement::index() const |
| { |
| // It would be faster to cache the index, but harder to get it right in all cases. |
| |
| RefPtr selectElement = ownerSelectElement(); |
| if (!selectElement) |
| return 0; |
| |
| int optionIndex = 0; |
| |
| for (auto& item : selectElement->listItems()) { |
| if (!is<HTMLOptionElement>(*item)) |
| continue; |
| if (item == this) |
| return optionIndex; |
| ++optionIndex; |
| } |
| |
| return 0; |
| } |
| |
| void HTMLOptionElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason) |
| { |
| switch (name.nodeName()) { |
| case AttributeNames::disabledAttr: { |
| bool newDisabled = !newValue.isNull(); |
| if (m_disabled != newDisabled) { |
| Style::PseudoClassChangeInvalidation disabledInvalidation(*this, { { CSSSelector::PseudoClass::Disabled, newDisabled }, { CSSSelector::PseudoClass::Enabled, !newDisabled } }); |
| m_disabled = newDisabled; |
| if (CheckedPtr renderer = this->renderer(); renderer && renderer->style().hasUsedAppearance()) |
| renderer->repaint(); |
| } |
| break; |
| } |
| case AttributeNames::selectedAttr: { |
| // FIXME: Use PseudoClassChangeInvalidation in other elements that implement matchesDefaultPseudoClass(). |
| Style::PseudoClassChangeInvalidation defaultInvalidation(*this, CSSSelector::PseudoClass::Default, !newValue.isNull()); |
| m_isDefault = !newValue.isNull(); |
| |
| // https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-selectedness |
| if (oldValue.isNull() != newValue.isNull() && !m_isDirty) |
| setSelected(!newValue.isNull()); |
| break; |
| } |
| case AttributeNames::labelAttr: { |
| if (RefPtr select = ownerSelectElement()) |
| select->optionElementChildrenChanged(); |
| invalidateShadowTree(); |
| break; |
| } |
| case AttributeNames::valueAttr: |
| for (Ref dataList : ancestorsOfType<HTMLDataListElement>(*this)) |
| dataList->optionElementChildrenChanged(); |
| break; |
| default: |
| HTMLElement::attributeChanged(name, oldValue, newValue, attributeModificationReason); |
| break; |
| } |
| } |
| |
| String HTMLOptionElement::value() const |
| { |
| const AtomString& value = attributeWithoutSynchronization(valueAttr); |
| if (!value.isNull()) |
| return value; |
| return collectOptionInnerTextCollapsingWhitespace(); |
| } |
| |
| bool HTMLOptionElement::selected(AllowStyleInvalidation allowStyleInvalidation) const |
| { |
| if (RefPtr select = ownerSelectElement()) |
| select->updateListItemSelectedStates(allowStyleInvalidation); |
| return m_isSelected; |
| } |
| |
| void HTMLOptionElement::setSelected(bool selected) |
| { |
| if (m_isSelected == selected) |
| return; |
| |
| setSelectedState(selected); |
| |
| if (RefPtr select = ownerSelectElement()) |
| select->optionSelectionStateChanged(*this, selected); |
| } |
| |
| bool HTMLOptionElement::selectedForBindings() const |
| { |
| return selected(); |
| } |
| |
| void HTMLOptionElement::setSelectedForBindings(bool selected) |
| { |
| bool wasSelected = m_isSelected; |
| setSelected(selected); |
| |
| // https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-dirtiness |
| // The spec says dirtiness becomes true unconditionally. However, for web |
| // compatibility, don't set dirtiness if the option is owned by a select |
| // element and selectedness did not actually change. |
| if (ownerSelectElement() && wasSelected == m_isSelected) |
| return; |
| |
| m_isDirty = true; |
| } |
| |
| void HTMLOptionElement::setSelectedState(bool selected, AllowStyleInvalidation allowStyleInvalidation) |
| { |
| if (m_isSelected == selected) |
| return; |
| |
| std::optional<Style::PseudoClassChangeInvalidation> checkedInvalidation; |
| if (allowStyleInvalidation == AllowStyleInvalidation::Yes) |
| emplace(checkedInvalidation, *this, { { CSSSelector::PseudoClass::Checked, selected } }); |
| |
| m_isSelected = selected; |
| |
| if (CheckedPtr cache = protect(document())->existingAXObjectCache()) |
| cache->onSelectedOptionChanged(*this); |
| } |
| |
| void HTMLOptionElement::childrenChanged(const ChildChange& change) |
| { |
| Vector<Ref<HTMLDataListElement>> ancestors; |
| for (Ref dataList : ancestorsOfType<HTMLDataListElement>(*this)) |
| ancestors.append(WTF::move(dataList)); |
| for (auto& dataList : ancestors) |
| dataList->optionElementChildrenChanged(); |
| if (change.source != ChildChange::Source::Clone) { |
| if (RefPtr select = ownerSelectElement()) |
| select->optionElementChildrenChanged(); |
| } |
| HTMLElement::childrenChanged(change); |
| } |
| |
| void HTMLOptionElement::willResetComputedStyle() |
| { |
| if (RefPtr select = ownerSelectElement()) { |
| if (CheckedPtr selectRenderer = select->renderer()) |
| selectRenderer->repaint(); |
| } |
| } |
| |
| HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const |
| { |
| if (document().settings().htmlEnhancedSelectParsingEnabled()) |
| return m_ownerSelect.get(); |
| |
| if (auto* parent = parentElement()) { |
| if (auto* select = dynamicDowncast<HTMLSelectElement>(*parent)) |
| return select; |
| if (auto* optGroup = dynamicDowncast<HTMLOptGroupElement>(*parent)) |
| return optGroup->ownerSelectElement(); |
| } |
| return nullptr; |
| } |
| |
| bool HTMLOptionElement::belongsToBaseAppearancePicker() const |
| { |
| RefPtr select = ownerSelectElement(); |
| return select && select->usesBaseAppearancePicker(); |
| } |
| |
| String HTMLOptionElement::label() const |
| { |
| String label = attributeWithoutSynchronization(labelAttr); |
| if (!label.isNull()) |
| return label; |
| return collectOptionInnerTextCollapsingWhitespace(); |
| } |
| |
| String HTMLOptionElement::displayLabel() const |
| { |
| String label = attributeWithoutSynchronization(labelAttr); |
| if (label.isEmpty()) |
| return collectOptionInnerTextCollapsingWhitespace(); |
| return label; |
| } |
| |
| String HTMLOptionElement::textIndentedToRespectGroupLabel() const |
| { |
| if (!document().settings().htmlEnhancedSelectParsingEnabled()) { |
| if (is<HTMLOptGroupElement>(parentNode())) |
| return makeString(" "_s, displayLabel()); |
| return displayLabel(); |
| } |
| |
| for (Ref ancestor : ancestorsOfType<HTMLElement>(*this)) { |
| if (is<HTMLOptGroupElement>(ancestor)) |
| return makeString(" "_s, displayLabel()); |
| |
| if (isAnyOf<HTMLDataListElement, HTMLSelectElement, HTMLOptionElement, HTMLHRElement>(ancestor)) |
| return displayLabel(); |
| } |
| return displayLabel(); |
| } |
| |
| bool HTMLOptionElement::isDisabledFormControl() const |
| { |
| if (ownElementDisabled()) |
| return true; |
| |
| if (!document().settings().htmlEnhancedSelectParsingEnabled()) { |
| RefPtr parentOptGroup = dynamicDowncast<HTMLOptGroupElement>(parentNode()); |
| return parentOptGroup && parentOptGroup->isDisabledFormControl(); |
| } |
| |
| for (Ref ancestor : ancestorsOfType<HTMLElement>(*this)) { |
| if (RefPtr optGroup = dynamicDowncast<HTMLOptGroupElement>(ancestor)) |
| return optGroup->isDisabledFormControl(); |
| if (isAnyOf<HTMLDataListElement, HTMLSelectElement, HTMLOptionElement, HTMLHRElement>(ancestor)) |
| return false; |
| } |
| return false; |
| } |
| |
| bool HTMLOptionElement::isActuallyDisabled() const |
| { |
| if (HTMLElement::isActuallyDisabled()) |
| return true; |
| RefPtr select = ownerSelectElement(); |
| return select && select->isDisabledFormControl(); |
| } |
| |
| String HTMLOptionElement::collectOptionInnerText() const |
| { |
| StringBuilder text; |
| // Text nodes inside script elements are not part of the option text. |
| for (RefPtr node = firstChild(); node; node = isScriptElement(*node) ? NodeTraversal::nextSkippingChildren(*node, this) : NodeTraversal::next(*node, this)) { |
| if (auto* textNode = dynamicDowncast<Text>(*node)) |
| text.append(textNode->data()); |
| } |
| return text.toString(); |
| } |
| |
| String HTMLOptionElement::collectOptionInnerTextCollapsingWhitespace() const |
| { |
| return collectOptionInnerText().trim(isASCIIWhitespace).simplifyWhiteSpace(isASCIIWhitespace); |
| } |
| |
| void HTMLOptionElement::cloneIntoSelectedContent(HTMLSelectedContentElement& selectedContent) |
| { |
| ASSERT(document().settings().htmlEnhancedSelectParsingEnabled()); |
| ASSERT(document().settings().htmlEnhancedSelectEnabled()); |
| ASSERT(!selectedContent.document().settings().mutationEventsEnabled()); |
| |
| NodeVector newChildren; |
| for (RefPtr child = firstChild(); child; child = child->nextSibling()) |
| newChildren.append(child->cloneNode(true)); |
| selectedContent.replaceChildrenWithoutValidityCheck(WTF::move(newChildren)); |
| } |
| |
| } // namespace |