From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- Source/WebCore/html/HTMLSelectElement.cpp | 767 +++++++++++++++--------------- 1 file changed, 378 insertions(+), 389 deletions(-) (limited to 'Source/WebCore/html/HTMLSelectElement.cpp') diff --git a/Source/WebCore/html/HTMLSelectElement.cpp b/Source/WebCore/html/HTMLSelectElement.cpp index 2c2eb89a8..a28e4c05d 100644 --- a/Source/WebCore/html/HTMLSelectElement.cpp +++ b/Source/WebCore/html/HTMLSelectElement.cpp @@ -29,24 +29,24 @@ #include "HTMLSelectElement.h" #include "AXObjectCache.h" -#include "Attribute.h" -#include "Chrome.h" -#include "ChromeClient.h" #include "ElementTraversal.h" #include "EventHandler.h" #include "EventNames.h" -#include "ExceptionCodePlaceholder.h" #include "FormController.h" #include "FormDataList.h" #include "Frame.h" +#include "GenericCachedHTMLCollection.h" #include "HTMLFormElement.h" +#include "HTMLHRElement.h" #include "HTMLNames.h" #include "HTMLOptGroupElement.h" #include "HTMLOptionElement.h" #include "HTMLOptionsCollection.h" +#include "HTMLParserIdioms.h" #include "KeyboardEvent.h" #include "LocalizedStrings.h" #include "MouseEvent.h" +#include "NodeRareData.h" #include "Page.h" #include "PlatformMouseEvent.h" #include "RenderListBox.h" @@ -80,10 +80,10 @@ HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document& doc ASSERT(hasTagName(selectTag)); } -PassRefPtr HTMLSelectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) +Ref HTMLSelectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) { ASSERT(tagName.matches(selectTag)); - return adoptRef(new HTMLSelectElement(tagName, document, form)); + return adoptRef(*new HTMLSelectElement(tagName, document, form)); } void HTMLSelectElement::didRecalcStyle(Style::Change styleChange) @@ -96,15 +96,15 @@ void HTMLSelectElement::didRecalcStyle(Style::Change styleChange) const AtomicString& HTMLSelectElement::formControlType() const { - DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral)); + static NeverDestroyed selectMultiple("select-multiple", AtomicString::ConstructFromLiteral); + static NeverDestroyed selectOne("select-one", AtomicString::ConstructFromLiteral); return m_multiple ? selectMultiple : selectOne; } void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) { deselectItemsWithoutValidation(excludeElement); - setNeedsValidityCheck(); + updateValidity(); } void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection) @@ -113,7 +113,7 @@ void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeN // This produces that same behavior for changes triggered by other code running on behalf of the user. if (!usesMenuList()) { updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false); - setNeedsValidityCheck(); + updateValidity(); if (fireOnChangeNow) listBoxOnChange(); return; @@ -149,8 +149,8 @@ bool HTMLSelectElement::hasPlaceholderLabelOption() const ASSERT(listIndex >= 0); if (listIndex < 0) return false; - HTMLOptionElement* option = toHTMLOptionElement(listItems()[listIndex]); - return !listIndex && option->value().isEmpty(); + HTMLOptionElement& option = downcast(*listItems()[listIndex]); + return !listIndex && option.value().isEmpty(); } String HTMLSelectElement::validationMessage() const @@ -184,7 +184,7 @@ void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelec optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false); else { updateSelectedState(listIndex, allowMultiplySelections, shift); - setNeedsValidityCheck(); + updateValidity(); if (fireOnChangeNow) listBoxOnChange(); } @@ -193,9 +193,7 @@ void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelec bool HTMLSelectElement::usesMenuList() const { #if !PLATFORM(IOS) - const Page* page = document().page(); - RefPtr renderTheme = page ? &page->theme() : RenderTheme::defaultTheme(); - if (renderTheme->delegatesMenuListRendering()) + if (RenderTheme::themeForPage(document().page())->delegatesMenuListRendering()) return true; return !m_multiple && m_size <= 1; @@ -218,66 +216,63 @@ int HTMLSelectElement::activeSelectionEndListIndex() const return lastSelectedListIndex(); } -void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec) +ExceptionOr HTMLSelectElement::add(const OptionOrOptGroupElement& element, const std::optional& before) { - if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) - return; + HTMLElement* beforeElement = nullptr; + if (before) { + beforeElement = WTF::switchOn(before.value(), + [](const RefPtr& element) -> HTMLElement* { return element.get(); }, + [this](int index) -> HTMLElement* { return item(index); } + ); + } + HTMLElement& toInsert = WTF::switchOn(element, + [](const auto& htmlElement) -> HTMLElement& { return *htmlElement; } + ); - // Make sure the element is ref'd and deref'd so we don't leak it. - Ref protectNewChild(*element); - insertBefore(element, before, ec); - setNeedsValidityCheck(); + return insertBefore(toInsert, beforeElement); } -void HTMLSelectElement::removeByIndex(int optionIndex) +void HTMLSelectElement::remove(int optionIndex) { int listIndex = optionToListIndex(optionIndex); if (listIndex < 0) return; - listItems()[listIndex]->remove(IGNORE_EXCEPTION); + listItems()[listIndex]->remove(); } -void HTMLSelectElement::remove(HTMLOptionElement* option) +ExceptionOr HTMLSelectElement::remove(HTMLOptionElement& option) { - if (option->ownerSelectElement() != this) - return; + if (option.ownerSelectElement() != this) + return { }; - option->remove(IGNORE_EXCEPTION); + return option.remove(); } String HTMLSelectElement::value() const { - const Vector& items = listItems(); - for (unsigned i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement* option = toHTMLOptionElement(items[i]); - if (option->selected()) - return option->value(); + for (auto* item : listItems()) { + if (is(*item)) { + HTMLOptionElement& option = downcast(*item); + if (option.selected()) + return option.value(); } } - return ""; + return emptyString(); } -void HTMLSelectElement::setValue(const String &value) +void HTMLSelectElement::setValue(const String& value) { - // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once. - if (value.isNull()) { - setSelectedIndex(-1); - return; - } - // Find the option with value() matching the given parameter and make it the current selection. - const Vector& items = listItems(); unsigned optionIndex = 0; - for (unsigned i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - if (toHTMLOptionElement(items[i])->value() == value) { + for (auto* item : listItems()) { + if (is(*item)) { + if (downcast(*item).value() == value) { setSelectedIndex(optionIndex); return; } - optionIndex++; + ++optionIndex; } } @@ -298,27 +293,19 @@ bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == sizeAttr) { - int oldSize = m_size; - // Set the attribute value to a number. - // This is important since the style rules for this attribute can determine the appearance property. - int size = value.toInt(); - AtomicString attrSize = AtomicString::number(size); - if (attrSize != value) { - // FIXME: This is horribly factored. - if (Attribute* sizeAttribute = ensureUniqueElementData().findAttributeByName(sizeAttr)) - sizeAttribute->setValue(attrSize); - } - size = std::max(size, 1); + unsigned oldSize = m_size; + unsigned size = limitToOnlyHTMLNonNegative(value); // Ensure that we've determined selectedness of the items at least once prior to changing the size. if (oldSize != size) updateListItemSelectedStates(); m_size = size; - setNeedsValidityCheck(); + updateValidity(); if (m_size != oldSize) { - setNeedsStyleRecalc(ReconstructRenderTree); + invalidateStyleAndRenderersForSubtree(); setRecalcListItems(); + updateValidity(); } } else if (name == multipleAttr) parseMultipleAttribute(value); @@ -329,7 +316,7 @@ void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicSt HTMLFormControlElementWithState::parseAttribute(name, value); } -bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const +bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent& event) const { if (renderer()) return isFocusable(); @@ -348,14 +335,14 @@ bool HTMLSelectElement::canSelectAll() const return !usesMenuList(); } -RenderPtr HTMLSelectElement::createElementRenderer(PassRef style) +RenderPtr HTMLSelectElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) { #if !PLATFORM(IOS) if (usesMenuList()) - return createRenderer(*this, std::move(style)); - return createRenderer(*this, std::move(style)); + return createRenderer(*this, WTFMove(style)); + return createRenderer(*this, WTFMove(style)); #else - return createRenderer(*this, std::move(style)); + return createRenderer(*this, WTFMove(style)); #endif } @@ -365,19 +352,19 @@ bool HTMLSelectElement::childShouldCreateRenderer(const Node& child) const return false; #if !PLATFORM(IOS) if (!usesMenuList()) - return isHTMLOptionElement(child) || isHTMLOptGroupElement(child) || validationMessageShadowTreeContains(child); + return is(child) || is(child) || validationMessageShadowTreeContains(child); #endif return validationMessageShadowTreeContains(child); } -PassRefPtr HTMLSelectElement::selectedOptions() +Ref HTMLSelectElement::selectedOptions() { - return ensureCachedHTMLCollection(SelectedOptions); + return ensureRareData().ensureNodeLists().addCachedCollection::traversalType>>(*this, SelectedOptions); } -PassRefPtr HTMLSelectElement::options() +Ref HTMLSelectElement::options() { - return static_cast(ensureCachedHTMLCollection(SelectOptions).get()); + return ensureRareData().ensureNodeLists().addCachedCollection(*this, SelectOptions); } void HTMLSelectElement::updateListItemSelectedStates() @@ -389,7 +376,7 @@ void HTMLSelectElement::updateListItemSelectedStates() void HTMLSelectElement::childrenChanged(const ChildChange& change) { setRecalcListItems(); - setNeedsValidityCheck(); + updateValidity(); m_lastOnChangeSelection.clear(); HTMLFormControlElementWithState::childrenChanged(change); @@ -398,12 +385,9 @@ void HTMLSelectElement::childrenChanged(const ChildChange& change) void HTMLSelectElement::optionElementChildrenChanged() { setRecalcListItems(); - setNeedsValidityCheck(); - - if (renderer()) { - if (AXObjectCache* cache = renderer()->document().existingAXObjectCache()) - cache->childrenChanged(this); - } + updateValidity(); + if (auto* cache = document().existingAXObjectCache()) + cache->childrenChanged(this); } void HTMLSelectElement::accessKeyAction(bool sendMouseEvents) @@ -416,7 +400,7 @@ void HTMLSelectElement::setMultiple(bool multiple) { bool oldMultiple = this->multiple(); int oldSelectedIndex = selectedIndex(); - setAttribute(multipleAttr, multiple ? "" : 0); + setAttributeWithoutSynchronization(multipleAttr, multiple ? emptyAtom : nullAtom); // Restore selectedIndex after changing the multiple flag to preserve // selection as single-line and multi-line has different defaults. @@ -424,81 +408,92 @@ void HTMLSelectElement::setMultiple(bool multiple) setSelectedIndex(oldSelectedIndex); } -void HTMLSelectElement::setSize(int size) +void HTMLSelectElement::setSize(unsigned size) { - setIntegralAttribute(sizeAttr, size); + setUnsignedIntegralAttribute(sizeAttr, limitToOnlyHTMLNonNegative(size)); } -Node* HTMLSelectElement::namedItem(const AtomicString& name) +HTMLOptionElement* HTMLSelectElement::namedItem(const AtomicString& name) { return options()->namedItem(name); } -Node* HTMLSelectElement::item(unsigned index) +HTMLOptionElement* HTMLSelectElement::item(unsigned index) { return options()->item(index); } -void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec) +ExceptionOr HTMLSelectElement::setItem(unsigned index, HTMLOptionElement* option) { - ec = 0; + if (!option) { + remove(index); + return { }; + } + if (index > maxSelectItems - 1) index = maxSelectItems - 1; + int diff = index - length(); - RefPtr before = 0; + + RefPtr before; // Out of array bounds? First insert empty dummies. if (diff > 0) { - setLength(index, ec); + auto result = setLength(index); + if (result.hasException()) + return result; // Replace an existing entry? } else if (diff < 0) { - before = toHTMLElement(options()->item(index+1)); - removeByIndex(index); + before = item(index + 1); + remove(index); } + // Finally add the new element. - if (!ec) { - add(option, before.get(), ec); - if (diff >= 0 && option->selected()) - optionSelectionStateChanged(option, true); - } + auto result = add(option, HTMLElementOrInt { before.get() }); + if (result.hasException()) + return result; + + if (diff >= 0 && option->selected()) + optionSelectionStateChanged(*option, true); + + return { }; } -void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec) +ExceptionOr HTMLSelectElement::setLength(unsigned newLength) { - ec = 0; - if (newLen > maxSelectItems) - newLen = maxSelectItems; - int diff = length() - newLen; + if (newLength > length() && newLength > maxSelectItems) { + document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, String::format("Blocked attempt to expand the option list to %u items. The maximum number of items allowed is %u.", newLength, maxSelectItems)); + return { }; + } + + int diff = length() - newLength; if (diff < 0) { // Add dummy elements. do { - RefPtr option = document().createElement(optionTag, false); - ASSERT(option); - add(toHTMLElement(option.get()), 0, ec); - if (ec) - break; + auto result = add(HTMLOptionElement::create(document()).ptr(), std::nullopt); + if (result.hasException()) + return result; } while (++diff); } else { - const Vector& items = listItems(); + auto& items = listItems(); // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list // of elements that we intend to remove then attempt to remove them one at a time. - Vector> itemsToRemove; + Vector> itemsToRemove; size_t optionIndex = 0; - for (size_t i = 0; i < items.size(); ++i) { - Element* item = items[i]; - if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) { + for (auto& item : items) { + if (is(*item) && optionIndex++ >= newLength) { ASSERT(item->parentNode()); - itemsToRemove.append(item); + itemsToRemove.append(downcast(*item)); } } - for (size_t i = 0; i < itemsToRemove.size(); ++i) { - Element* item = itemsToRemove[i].get(); - if (item->parentNode()) - item->parentNode()->removeChild(item, ec); - } + // FIXME: Clients can detect what order we remove the options in; is it good to remove them in ascending order? + // FIXME: This ignores exceptions. A previous version passed through the exception only for the last item removed. + // What exception behavior do we want? + for (auto& item : itemsToRemove) + item->remove(); } - setNeedsValidityCheck(); + return { }; } bool HTMLSelectElement::isRequiredFormControl() const @@ -506,12 +501,14 @@ bool HTMLSelectElement::isRequiredFormControl() const return isRequired(); } -#if PLATFORM(IOS) bool HTMLSelectElement::willRespondToMouseClickEvents() { +#if PLATFORM(IOS) return !isDisabledFormControl(); -} +#else + return HTMLFormControlElementWithState::willRespondToMouseClickEvents(); #endif +} // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one. // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one. @@ -520,12 +517,12 @@ bool HTMLSelectElement::willRespondToMouseClickEvents() int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const { ASSERT(direction == -1 || direction == 1); - const Vector& listItems = this->listItems(); + auto& listItems = this->listItems(); int lastGoodIndex = listIndex; int size = listItems.size(); for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) { --skip; - if (!listItems[listIndex]->isDisabledFormControl() && isHTMLOptionElement(listItems[listIndex])) { + if (!listItems[listIndex]->isDisabledFormControl() && is(*listItems[listIndex])) { lastGoodIndex = listIndex; if (skip <= 0) break; @@ -548,7 +545,7 @@ int HTMLSelectElement::previousSelectableListIndex(int startIndex) const int HTMLSelectElement::firstSelectableListIndex() const { - const Vector& items = listItems(); + auto& items = listItems(); int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX); if (static_cast(index) == items.size()) return -1; @@ -563,17 +560,19 @@ int HTMLSelectElement::lastSelectableListIndex() const // Returns the index of the next valid item one page away from |startIndex| in direction |direction|. int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const { - const Vector& items = listItems(); + auto& items = listItems(); + // Can't use m_size because renderer forces a minimum size. int pageSize = 0; - if (renderer()->isListBox()) - pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context. + auto* renderer = this->renderer(); + if (is(*renderer)) + pageSize = downcast(*renderer).size() - 1; // -1 so we still show context. // One page away, but not outside valid bounds. // If there is a valid option item one page away, the index is chosen. // If there is no exact one page away valid option, returns startIndex or the most far index. - int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1); - int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex)); + int edgeIndex = direction == SkipForwards ? 0 : items.size() - 1; + int skipAmount = pageSize + (direction == SkipForwards ? startIndex : edgeIndex - startIndex); return nextValidIndex(edgeIndex, direction, skipAmount); } @@ -595,7 +594,7 @@ void HTMLSelectElement::selectAll() updateListBoxSelection(false); listBoxOnChange(); - setNeedsValidityCheck(); + updateValidity(); } void HTMLSelectElement::saveLastSelection() @@ -606,11 +605,8 @@ void HTMLSelectElement::saveLastSelection() } m_lastOnChangeSelection.clear(); - const Vector& items = listItems(); - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - m_lastOnChangeSelection.append(isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected()); - } + for (auto& element : listItems()) + m_lastOnChangeSelection.append(is(*element) && downcast(*element).selected()); } void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) @@ -621,11 +617,8 @@ void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) // selection pivots around this anchor index. m_cachedStateForActiveSelection.clear(); - const Vector& items = listItems(); - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - m_cachedStateForActiveSelection.append(isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected()); - } + for (auto& element : listItems()) + m_cachedStateForActiveSelection.append(is(*element) && downcast(*element).selected()); } void HTMLSelectElement::setActiveSelectionEndIndex(int index) @@ -635,40 +628,42 @@ void HTMLSelectElement::setActiveSelectionEndIndex(int index) void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) { + ASSERT(renderer()); + #if !PLATFORM(IOS) - ASSERT(renderer() && (renderer()->isListBox() || m_multiple)); + ASSERT(renderer()->isListBox() || m_multiple); #else - ASSERT(renderer() && (renderer()->isMenuList() || m_multiple)); + ASSERT(renderer()->isMenuList() || m_multiple); #endif + ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0); unsigned start = std::min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); unsigned end = std::max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); - const Vector& items = listItems(); + auto& items = listItems(); for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - if (!isHTMLOptionElement(element) || toHTMLOptionElement(element)->isDisabledFormControl()) + auto& element = *items[i]; + if (!is(element) || downcast(element).isDisabledFormControl()) continue; if (i >= start && i <= end) - toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState); + downcast(element).setSelectedState(m_activeSelectionState); else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size()) - toHTMLOptionElement(element)->setSelectedState(false); + downcast(element).setSelectedState(false); else - toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]); + downcast(element).setSelectedState(m_cachedStateForActiveSelection[i]); } scrollToSelection(); - setNeedsValidityCheck(); - notifyFormStateChanged(); + updateValidity(); } void HTMLSelectElement::listBoxOnChange() { ASSERT(!usesMenuList() || m_multiple); - const Vector& items = listItems(); + auto& items = listItems(); // If the cached selection list is empty, or the size has changed, then fire // dispatchFormControlChangeEvent, and return early. @@ -680,15 +675,17 @@ void HTMLSelectElement::listBoxOnChange() // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent. bool fireOnChange = false; for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - bool selected = isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected(); + auto& element = *items[i]; + bool selected = is(element) && downcast(element).selected(); if (selected != m_lastOnChangeSelection[i]) fireOnChange = true; m_lastOnChangeSelection[i] = selected; } - if (fireOnChange) + if (fireOnChange) { + dispatchInputEvent(); dispatchFormControlChangeEvent(); + } } void HTMLSelectElement::dispatchChangeEventForMenuList() @@ -699,6 +696,7 @@ void HTMLSelectElement::dispatchChangeEventForMenuList() if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) { m_lastOnChangeIndex = selected; m_isProcessingUserDrivenChange = false; + dispatchInputEvent(); dispatchFormControlChangeEvent(); } } @@ -709,26 +707,26 @@ void HTMLSelectElement::scrollToSelection() if (usesMenuList()) return; - auto renderer = this->renderer(); - if (!renderer || !renderer->isListBox()) + auto* renderer = this->renderer(); + if (!is(renderer)) return; - toRenderListBox(renderer)->selectionChanged(); + downcast(*renderer).selectionChanged(); #else - if (auto renderer = this->renderer()) + if (auto* renderer = this->renderer()) renderer->repaint(); #endif } void HTMLSelectElement::setOptionsChangedOnRenderer() { - if (auto renderer = this->renderer()) { + if (auto* renderer = this->renderer()) { #if !PLATFORM(IOS) - if (renderer->isMenuList()) - toRenderMenuList(renderer)->setOptionsChanged(true); + if (is(*renderer)) + downcast(*renderer).setOptionsChanged(true); else - toRenderListBox(renderer)->setOptionsChanged(true); + downcast(*renderer).setOptionsChanged(true); #else - toRenderMenuList(renderer)->setOptionsChanged(true); + downcast(*renderer).setOptionsChanged(true); #endif } } @@ -751,7 +749,7 @@ const Vector& HTMLSelectElement::listItems() const void HTMLSelectElement::invalidateSelectedItems() { if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions)) - collection->invalidateCache(); + collection->invalidateCache(document()); } void HTMLSelectElement::setRecalcListItems() @@ -760,18 +758,15 @@ void HTMLSelectElement::setRecalcListItems() // Manual selection anchor is reset when manipulating the select programmatically. m_activeSelectionAnchorIndex = -1; setOptionsChangedOnRenderer(); - setNeedsStyleRecalc(); - if (!inDocument()) { + invalidateStyleForSubtree(); + if (!isConnected()) { if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions)) - collection->invalidateCache(); + collection->invalidateCache(document()); } - if (!inDocument()) + if (!isConnected()) invalidateSelectedItems(); - - if (renderer()) { - if (AXObjectCache* cache = renderer()->document().existingAXObjectCache()) - cache->childrenChanged(this); - } + if (auto* cache = document().existingAXObjectCache()) + cache->childrenChanged(this); } void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const @@ -782,44 +777,42 @@ void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const HTMLOptionElement* foundSelected = 0; HTMLOptionElement* firstOption = 0; - for (Element* currentElement = ElementTraversal::firstWithin(this); currentElement; ) { - if (!currentElement->isHTMLElement()) { - currentElement = ElementTraversal::nextSkippingChildren(currentElement, this); + for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) { + if (!is(*currentElement)) { + currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this); continue; } - HTMLElement* current = toHTMLElement(currentElement); + HTMLElement& current = downcast(*currentElement); - // optgroup tags may not nest. However, both FireFox and IE will - // flatten the tree automatically, so we follow suit. - // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6) - if (isHTMLOptGroupElement(current)) { - m_listItems.append(current); + // Only consider optgroup elements that are direct children of the select element. + if (is(current) && current.parentNode() == this) { + m_listItems.append(¤t); if (Element* nextElement = ElementTraversal::firstWithin(current)) { currentElement = nextElement; continue; } } - if (isHTMLOptionElement(current)) { - m_listItems.append(current); + if (is(current)) { + m_listItems.append(¤t); if (updateSelectedStates && !m_multiple) { - HTMLOptionElement* option = toHTMLOptionElement(current); + HTMLOptionElement& option = downcast(current); if (!firstOption) - firstOption = option; - if (option->selected()) { + firstOption = &option; + if (option.selected()) { if (foundSelected) foundSelected->setSelectedState(false); - foundSelected = option; - } else if (m_size <= 1 && !foundSelected && !option->isDisabledFormControl()) { - foundSelected = option; + foundSelected = &option; + } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) { + foundSelected = &option; foundSelected->setSelectedState(true); } } } - if (current->hasTagName(hrTag)) - m_listItems.append(current); + if (current.hasTagName(hrTag)) + m_listItems.append(¤t); // In conforming HTML code, only and