diff options
Diffstat (limited to 'Source/WebCore/html/HTMLSelectElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLSelectElement.cpp | 767 |
1 files changed, 378 insertions, 389 deletions
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> HTMLSelectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) +Ref<HTMLSelectElement> 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<const AtomicString> selectMultiple("select-multiple", AtomicString::ConstructFromLiteral); + static NeverDestroyed<const AtomicString> 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<HTMLOptionElement>(*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> 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<void> HTMLSelectElement::add(const OptionOrOptGroupElement& element, const std::optional<HTMLElementOrInt>& before) { - if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) - return; + HTMLElement* beforeElement = nullptr; + if (before) { + beforeElement = WTF::switchOn(before.value(), + [](const RefPtr<HTMLElement>& 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<HTMLElement> 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<void> 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<HTMLElement*>& 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<HTMLOptionElement>(*item)) { + HTMLOptionElement& option = downcast<HTMLOptionElement>(*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<HTMLElement*>& 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<HTMLOptionElement>(*item)) { + if (downcast<HTMLOptionElement>(*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<RenderElement> HTMLSelectElement::createElementRenderer(PassRef<RenderStyle> style) +RenderPtr<RenderElement> HTMLSelectElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) { #if !PLATFORM(IOS) if (usesMenuList()) - return createRenderer<RenderMenuList>(*this, std::move(style)); - return createRenderer<RenderListBox>(*this, std::move(style)); + return createRenderer<RenderMenuList>(*this, WTFMove(style)); + return createRenderer<RenderListBox>(*this, WTFMove(style)); #else - return createRenderer<RenderMenuList>(*this, std::move(style)); + return createRenderer<RenderMenuList>(*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<HTMLOptionElement>(child) || is<HTMLOptGroupElement>(child) || validationMessageShadowTreeContains(child); #endif return validationMessageShadowTreeContains(child); } -PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions() +Ref<HTMLCollection> HTMLSelectElement::selectedOptions() { - return ensureCachedHTMLCollection(SelectedOptions); + return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<SelectedOptions>::traversalType>>(*this, SelectedOptions); } -PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() +Ref<HTMLOptionsCollection> HTMLSelectElement::options() { - return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get()); + return ensureRareData().ensureNodeLists().addCachedCollection<HTMLOptionsCollection>(*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<void> 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<HTMLElement> before = 0; + + RefPtr<HTMLOptionElement> 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<void> 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<Element> 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<HTMLElement*>& 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<RefPtr<Element>> itemsToRemove; + Vector<Ref<HTMLOptionElement>> 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<HTMLOptionElement>(*item) && optionIndex++ >= newLength) { ASSERT(item->parentNode()); - itemsToRemove.append(item); + itemsToRemove.append(downcast<HTMLOptionElement>(*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<HTMLElement*>& 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<HTMLOptionElement>(*listItems[listIndex])) { lastGoodIndex = listIndex; if (skip <= 0) break; @@ -548,7 +545,7 @@ int HTMLSelectElement::previousSelectableListIndex(int startIndex) const int HTMLSelectElement::firstSelectableListIndex() const { - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX); if (static_cast<size_t>(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<HTMLElement*>& 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<RenderListBox>(*renderer)) + pageSize = downcast<RenderListBox>(*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<HTMLElement*>& 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<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*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<HTMLElement*>& 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<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*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<HTMLElement*>& 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<HTMLOptionElement>(element) || downcast<HTMLOptionElement>(element).isDisabledFormControl()) continue; if (i >= start && i <= end) - toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState); + downcast<HTMLOptionElement>(element).setSelectedState(m_activeSelectionState); else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size()) - toHTMLOptionElement(element)->setSelectedState(false); + downcast<HTMLOptionElement>(element).setSelectedState(false); else - toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]); + downcast<HTMLOptionElement>(element).setSelectedState(m_cachedStateForActiveSelection[i]); } scrollToSelection(); - setNeedsValidityCheck(); - notifyFormStateChanged(); + updateValidity(); } void HTMLSelectElement::listBoxOnChange() { ASSERT(!usesMenuList() || m_multiple); - const Vector<HTMLElement*>& 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<HTMLOptionElement>(element) && downcast<HTMLOptionElement>(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<RenderListBox>(renderer)) return; - toRenderListBox(renderer)->selectionChanged(); + downcast<RenderListBox>(*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<RenderMenuList>(*renderer)) + downcast<RenderMenuList>(*renderer).setOptionsChanged(true); else - toRenderListBox(renderer)->setOptionsChanged(true); + downcast<RenderListBox>(*renderer).setOptionsChanged(true); #else - toRenderMenuList(renderer)->setOptionsChanged(true); + downcast<RenderMenuList>(*renderer).setOptionsChanged(true); #endif } } @@ -751,7 +749,7 @@ const Vector<HTMLElement*>& 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<HTMLElement>(*currentElement)) { + currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this); continue; } - HTMLElement* current = toHTMLElement(currentElement); + HTMLElement& current = downcast<HTMLElement>(*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<HTMLOptGroupElement>(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<HTMLOptionElement>(current)) { + m_listItems.append(¤t); if (updateSelectedStates && !m_multiple) { - HTMLOptionElement* option = toHTMLOptionElement(current); + HTMLOptionElement& option = downcast<HTMLOptionElement>(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 <optgroup> and <option> will be found // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step @@ -827,7 +820,7 @@ void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const // with the case where odd tags like a <div> have been added but we // handle this because such tags have already been removed from the // <select>'s subtree at this point. - currentElement = ElementTraversal::nextSkippingChildren(currentElement, this); + currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this); } if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected()) @@ -839,11 +832,9 @@ int HTMLSelectElement::selectedIndex() const unsigned index = 0; // Return the number of the first option selected. - const Vector<HTMLElement*>& items = listItems(); - for (size_t i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - if (isHTMLOptionElement(element)) { - if (toHTMLOptionElement(element)->selected()) + for (auto& element : listItems()) { + if (is<HTMLOptionElement>(*element)) { + if (downcast<HTMLOptionElement>(*element).selected()) return index; ++index; } @@ -857,11 +848,11 @@ void HTMLSelectElement::setSelectedIndex(int index) selectOption(index, DeselectOtherOptions); } -void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected) +void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement& option, bool optionIsSelected) { - ASSERT(option->ownerSelectElement() == this); + ASSERT(option.ownerSelectElement() == this); if (optionIsSelected) - selectOption(option->index()); + selectOption(option.index()); else if (!usesMenuList()) selectOption(-1); else @@ -872,26 +863,28 @@ void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags) { bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions); - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); int listIndex = optionToListIndex(optionIndex); - HTMLElement* element = 0; - if (listIndex >= 0) { + HTMLElement* element = nullptr; + if (listIndex >= 0) element = items[listIndex]; - if (isHTMLOptionElement(element)) { - if (m_activeSelectionAnchorIndex < 0 || shouldDeselect) - setActiveSelectionAnchorIndex(listIndex); - if (m_activeSelectionEndIndex < 0 || shouldDeselect) - setActiveSelectionEndIndex(listIndex); - toHTMLOptionElement(element)->setSelectedState(true); - } - } if (shouldDeselect) deselectItemsWithoutValidation(element); + if (is<HTMLOptionElement>(element)) { + if (m_activeSelectionAnchorIndex < 0 || shouldDeselect) + setActiveSelectionAnchorIndex(listIndex); + if (m_activeSelectionEndIndex < 0 || shouldDeselect) + setActiveSelectionEndIndex(listIndex); + downcast<HTMLOptionElement>(*element).setSelectedState(true); + } + + updateValidity(); + // For the menu list case, this is what makes the selected element appear. - if (auto renderer = this->renderer()) + if (auto* renderer = this->renderer()) renderer->updateFromElement(); scrollToSelection(); @@ -900,28 +893,25 @@ void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags) m_isProcessingUserDrivenChange = flags & UserDriven; if (flags & DispatchChangeEvent) dispatchChangeEventForMenuList(); - if (auto renderer = this->renderer()) { - if (renderer->isMenuList()) - toRenderMenuList(renderer)->didSetSelectedIndex(listIndex); + if (auto* renderer = this->renderer()) { + if (is<RenderMenuList>(*renderer)) + downcast<RenderMenuList>(*renderer).didSetSelectedIndex(listIndex); else - toRenderListBox(renderer)->selectionChanged(); + downcast<RenderListBox>(*renderer).selectionChanged(); } } - - setNeedsValidityCheck(); - notifyFormStateChanged(); } int HTMLSelectElement::optionToListIndex(int optionIndex) const { - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); int listSize = static_cast<int>(items.size()); if (optionIndex < 0 || optionIndex >= listSize) return -1; int optionIndex2 = -1; for (int listIndex = 0; listIndex < listSize; ++listIndex) { - if (isHTMLOptionElement(items[listIndex])) { + if (is<HTMLOptionElement>(*items[listIndex])) { ++optionIndex2; if (optionIndex2 == optionIndex) return listIndex; @@ -933,61 +923,57 @@ int HTMLSelectElement::optionToListIndex(int optionIndex) const int HTMLSelectElement::listToOptionIndex(int listIndex) const { - const Vector<HTMLElement*>& items = listItems(); - if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(items[listIndex])) + auto& items = listItems(); + if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !is<HTMLOptionElement>(*items[listIndex])) return -1; // Actual index of option not counting OPTGROUP entries that may be in list. int optionIndex = 0; for (int i = 0; i < listIndex; ++i) { - if (isHTMLOptionElement(items[i])) + if (is<HTMLOptionElement>(*items[i])) ++optionIndex; } return optionIndex; } -void HTMLSelectElement::dispatchFocusEvent(PassRefPtr<Element> oldFocusedElement, FocusDirection direction) +void HTMLSelectElement::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection direction) { // Save the selection so it can be compared to the new selection when // dispatching change events during blur event dispatch. if (usesMenuList()) saveLastSelection(); - HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, direction); + HTMLFormControlElementWithState::dispatchFocusEvent(WTFMove(oldFocusedElement), direction); } -void HTMLSelectElement::dispatchBlurEvent(PassRefPtr<Element> newFocusedElement) +void HTMLSelectElement::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement) { // We only need to fire change events here for menu lists, because we fire // change events for list boxes whenever the selection change is actually made. // This matches other browsers' behavior. if (usesMenuList()) dispatchChangeEventForMenuList(); - HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement); + HTMLFormControlElementWithState::dispatchBlurEvent(WTFMove(newFocusedElement)); } void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement) { - const Vector<HTMLElement*>& items = listItems(); - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - if (element != excludeElement && isHTMLOptionElement(element)) - toHTMLOptionElement(element)->setSelectedState(false); + for (auto& element : listItems()) { + if (element != excludeElement && is<HTMLOptionElement>(*element)) + downcast<HTMLOptionElement>(*element).setSelectedState(false); } } FormControlState HTMLSelectElement::saveFormControlState() const { - const Vector<HTMLElement*>& items = listItems(); - size_t length = items.size(); FormControlState state; - for (unsigned i = 0; i < length; ++i) { - if (!isHTMLOptionElement(items[i])) + for (auto& element : listItems()) { + if (!is<HTMLOptionElement>(*element)) continue; - HTMLOptionElement* option = toHTMLOptionElement(items[i]); - if (!option->selected()) + HTMLOptionElement& option = downcast<HTMLOptionElement>(*element); + if (!option.selected()) continue; - state.append(option->value()); + state.append(option.value()); if (!multiple()) break; } @@ -996,12 +982,12 @@ FormControlState HTMLSelectElement::saveFormControlState() const size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const { - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); size_t loopEndIndex = std::min(items.size(), listIndexEnd); for (size_t i = listIndexStart; i < loopEndIndex; ++i) { - if (!items[i]->hasLocalName(optionTag)) + if (!is<HTMLOptionElement>(*items[i])) continue; - if (toHTMLOptionElement(items[i])->value() == value) + if (downcast<HTMLOptionElement>(*items[i]).value() == value) return i; } return notFound; @@ -1011,21 +997,21 @@ void HTMLSelectElement::restoreFormControlState(const FormControlState& state) { recalcListItems(); - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); size_t itemsSize = items.size(); if (!itemsSize) return; - for (size_t i = 0; i < itemsSize; ++i) { - if (!items[i]->hasLocalName(optionTag)) + for (auto& element : items) { + if (!is<HTMLOptionElement>(*element)) continue; - toHTMLOptionElement(items[i])->setSelectedState(false); + downcast<HTMLOptionElement>(*element).setSelectedState(false); } if (!multiple()) { size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize); if (foundIndex != notFound) - toHTMLOptionElement(items[foundIndex])->setSelectedState(true); + downcast<HTMLOptionElement>(*items[foundIndex]).setSelectedState(true); } else { size_t startIndex = 0; for (size_t i = 0; i < state.valueSize(); ++i) { @@ -1035,22 +1021,22 @@ void HTMLSelectElement::restoreFormControlState(const FormControlState& state) foundIndex = searchOptionsForValue(value, 0, startIndex); if (foundIndex == notFound) continue; - toHTMLOptionElement(items[foundIndex])->setSelectedState(true); + downcast<HTMLOptionElement>(*items[foundIndex]).setSelectedState(true); startIndex = foundIndex + 1; } } setOptionsChangedOnRenderer(); - setNeedsValidityCheck(); + updateValidity(); } void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value) { bool oldUsesMenuList = usesMenuList(); m_multiple = !value.isNull(); - setNeedsValidityCheck(); + updateValidity(); if (oldUsesMenuList != usesMenuList()) - setNeedsStyleRecalc(ReconstructRenderTree); + invalidateStyleAndRenderersForSubtree(); } bool HTMLSelectElement::appendFormData(FormDataList& list, bool) @@ -1060,12 +1046,9 @@ bool HTMLSelectElement::appendFormData(FormDataList& list, bool) return false; bool successful = false; - const Vector<HTMLElement*>& items = listItems(); - - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - if (isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->isDisabledFormControl()) { - list.appendData(name, toHTMLOptionElement(element)->value()); + for (auto& element : listItems()) { + if (is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected() && !downcast<HTMLOptionElement>(*element).isDisabledFormControl()) { + list.appendData(name, downcast<HTMLOptionElement>(*element).value()); successful = true; } } @@ -1078,42 +1061,39 @@ bool HTMLSelectElement::appendFormData(FormDataList& list, bool) void HTMLSelectElement::reset() { - HTMLOptionElement* firstOption = 0; - HTMLOptionElement* selectedOption = 0; + HTMLOptionElement* firstOption = nullptr; + HTMLOptionElement* selectedOption = nullptr; - const Vector<HTMLElement*>& items = listItems(); - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - if (!isHTMLOptionElement(element)) + for (auto& element : listItems()) { + if (!is<HTMLOptionElement>(*element)) continue; - if (items[i]->fastHasAttribute(selectedAttr)) { + HTMLOptionElement& option = downcast<HTMLOptionElement>(*element); + if (option.hasAttributeWithoutSynchronization(selectedAttr)) { if (selectedOption && !m_multiple) selectedOption->setSelectedState(false); - toHTMLOptionElement(element)->setSelectedState(true); - selectedOption = toHTMLOptionElement(element); + option.setSelectedState(true); + selectedOption = &option; } else - toHTMLOptionElement(element)->setSelectedState(false); + option.setSelectedState(false); if (!firstOption) - firstOption = toHTMLOptionElement(element); + firstOption = &option; } if (!selectedOption && firstOption && !m_multiple && m_size <= 1) firstOption->setSelectedState(true); setOptionsChangedOnRenderer(); - setNeedsStyleRecalc(); - setNeedsValidityCheck(); + invalidateStyleForSubtree(); + updateValidity(); } #if !PLATFORM(WIN) + bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event) { - const Page* page = document().page(); - RefPtr<RenderTheme> renderTheme = page ? &page->theme() : RenderTheme::defaultTheme(); - - if (!renderTheme->popsMenuByArrowKeys()) + if (!RenderTheme::themeForPage(document().page())->popsMenuByArrowKeys()) return false; if (!isSpatialNavigationEnabled(document().frame())) { @@ -1122,7 +1102,8 @@ bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event) // Calling focus() may cause us to lose our renderer. Return true so // that our caller doesn't process the event further, but don't set // the event as handled. - if (!renderer() || !renderer()->isMenuList()) + auto* renderer = this->renderer(); + if (!is<RenderMenuList>(renderer)) return true; // Save the selection so it can be compared to the new selection @@ -1130,7 +1111,7 @@ bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event) // gets called from RenderMenuList::valueChanged, which gets called // after the user makes a selection from the menu. saveLastSelection(); - toRenderMenuList(renderer())->showPopup(); + downcast<RenderMenuList>(*renderer).showPopup(); event->setDefaultHandled(); } return true; @@ -1138,20 +1119,22 @@ bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event) return false; } + #endif -void HTMLSelectElement::menuListDefaultEventHandler(Event* event) +void HTMLSelectElement::menuListDefaultEventHandler(Event& event) { - ASSERT(renderer() && renderer()->isMenuList()); + ASSERT(renderer()); + ASSERT(renderer()->isMenuList()); - const Page* page = document().page(); - RefPtr<RenderTheme> renderTheme = page ? &page->theme() : RenderTheme::defaultTheme(); + RefPtr<RenderTheme> renderTheme = RenderTheme::themeForPage(document().page()); - if (event->type() == eventNames().keydownEvent) { - if (!event->isKeyboardEvent()) + if (event.type() == eventNames().keydownEvent) { + if (!is<KeyboardEvent>(event)) return; - if (platformHandleKeydownEvent(static_cast<KeyboardEvent*>(event))) + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + if (platformHandleKeydownEvent(&keyboardEvent)) return; // When using spatial navigation, we want to be able to navigate away @@ -1162,15 +1145,14 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) return; } - const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); + const String& keyIdentifier = keyboardEvent.keyIdentifier(); bool handled = true; - const Vector<HTMLElement*>& listItems = this->listItems(); + auto& listItems = this->listItems(); int listIndex = optionToListIndex(selectedIndex()); // When using caret browsing, we want to be able to move the focus // out of the select element when user hits a left or right arrow key. - const Frame* frame = document().frame(); - if (frame && frame->settings().caretBrowsingEnabled()) { + if (document().settings().caretBrowsingEnabled()) { if (keyIdentifier == "Left" || keyIdentifier == "Right") return; } @@ -1194,22 +1176,23 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven); if (handled) - event->setDefaultHandled(); + keyboardEvent.setDefaultHandled(); } // Use key press event here since sending simulated mouse events // on key down blocks the proper sending of the key press event. - if (event->type() == eventNames().keypressEvent) { - if (!event->isKeyboardEvent()) + if (event.type() == eventNames().keypressEvent) { + if (!is<KeyboardEvent>(event)) return; - int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + int keyCode = keyboardEvent.keyCode(); bool handled = false; if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) { // Use space to toggle arrow key handling for selection change or spatial navigation. m_activeSelectionState = !m_activeSelectionState; - event->setDefaultHandled(); + keyboardEvent.setDefaultHandled(); return; } @@ -1217,9 +1200,9 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) if (keyCode == ' ' || keyCode == '\r') { focus(); - // Calling focus() may remove the renderer or change the - // renderer type. - if (!renderer() || !renderer()->isMenuList()) + // Calling focus() may remove the renderer or change the renderer type. + auto* renderer = this->renderer(); + if (!is<RenderMenuList>(renderer)) return; // Save the selection so it can be compared to the new selection @@ -1227,16 +1210,16 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) // gets called from RenderMenuList::valueChanged, which gets called // after the user makes a selection from the menu. saveLastSelection(); - toRenderMenuList(renderer())->showPopup(); + downcast<RenderMenuList>(*renderer).showPopup(); handled = true; } } else if (renderTheme->popsMenuByArrowKeys()) { if (keyCode == ' ') { focus(); - // Calling focus() may remove the renderer or change the - // renderer type. - if (!renderer() || !renderer()->isMenuList()) + // Calling focus() may remove the renderer or change the renderer type. + auto* renderer = this->renderer(); + if (!is<RenderMenuList>(renderer)) return; // Save the selection so it can be compared to the new selection @@ -1244,44 +1227,42 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) // gets called from RenderMenuList::valueChanged, which gets called // after the user makes a selection from the menu. saveLastSelection(); - toRenderMenuList(renderer())->showPopup(); + downcast<RenderMenuList>(*renderer).showPopup(); handled = true; } else if (keyCode == '\r') { if (form()) - form()->submitImplicitly(event, false); + form()->submitImplicitly(keyboardEvent, false); dispatchChangeEventForMenuList(); handled = true; } } if (handled) - event->setDefaultHandled(); + keyboardEvent.setDefaultHandled(); } - if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { focus(); #if !PLATFORM(IOS) - if (renderer() && renderer()->isMenuList()) { - auto& menuList = toRenderMenuList(*renderer()); - if (menuList.popupIsVisible()) - menuList.hidePopup(); - else { - // Save the selection so it can be compared to the new - // selection when we call onChange during selectOption, - // which gets called from RenderMenuList::valueChanged, - // which gets called after the user makes a selection from - // the menu. - saveLastSelection(); - menuList.showPopup(); - } + auto* renderer = this->renderer(); + if (is<RenderMenuList>(renderer)) { + auto& menuList = downcast<RenderMenuList>(*renderer); + ASSERT(!menuList.popupIsVisible()); + // Save the selection so it can be compared to the new + // selection when we call onChange during selectOption, + // which gets called from RenderMenuList::valueChanged, + // which gets called after the user makes a selection from + // the menu. + saveLastSelection(); + menuList.showPopup(); } #endif - event->setDefaultHandled(); + event.setDefaultHandled(); } #if !PLATFORM(IOS) - if (event->type() == eventNames().blurEvent && !focused()) { - auto& menuList = toRenderMenuList(*renderer()); + if (event.type() == eventNames().blurEvent && !focused()) { + auto& menuList = downcast<RenderMenuList>(*renderer()); if (menuList.popupIsVisible()) menuList.hidePopup(); } @@ -1290,7 +1271,10 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event) void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift) { - ASSERT(listIndex >= 0); + auto& items = listItems(); + int listSize = static_cast<int>(items.size()); + if (listIndex < 0 || listIndex >= listSize) + return; // Save the selection so it can be compared to the new selection when // dispatching change events during mouseup, or after autoscroll finishes. @@ -1301,21 +1285,21 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif bool shiftSelect = m_multiple && shift; bool multiSelect = m_multiple && multi && !shift; - HTMLElement* clickedElement = listItems()[listIndex]; - if (isHTMLOptionElement(clickedElement)) { + auto& clickedElement = *items[listIndex]; + if (is<HTMLOptionElement>(clickedElement)) { // Keep track of whether an active selection (like during drag // selection), should select or deselect. - if (toHTMLOptionElement(clickedElement)->selected() && multiSelect) + if (downcast<HTMLOptionElement>(clickedElement).selected() && multiSelect) m_activeSelectionState = false; if (!m_activeSelectionState) - toHTMLOptionElement(clickedElement)->setSelectedState(false); + downcast<HTMLOptionElement>(clickedElement).setSelectedState(false); } // If we're not in any special multiple selection mode, then deselect all // other items, excluding the clicked option. If no option was clicked, then // this will deselect all items in the list. if (!shiftSelect && !multiSelect) - deselectItemsWithoutValidation(clickedElement); + deselectItemsWithoutValidation(&clickedElement); // If the anchor hasn't been set, and we're doing a single selection or a // shift selection, then initialize the anchor to the first selected index. @@ -1323,8 +1307,8 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif setActiveSelectionAnchorIndex(selectedIndex()); // Set the selection state of the clicked option. - if (isHTMLOptionElement(clickedElement) && !toHTMLOptionElement(clickedElement)->isDisabledFormControl()) - toHTMLOptionElement(clickedElement)->setSelectedState(true); + if (is<HTMLOptionElement>(clickedElement) && !downcast<HTMLOptionElement>(clickedElement).isDisabledFormControl()) + downcast<HTMLOptionElement>(clickedElement).setSelectedState(true); // If there was no selectedIndex() for the previous initialization, or If // we're doing a single selection, or a multiple selection (using cmd or @@ -1337,40 +1321,44 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif updateListBoxSelection(!multiSelect); } -void HTMLSelectElement::listBoxDefaultEventHandler(Event* event) +void HTMLSelectElement::listBoxDefaultEventHandler(Event& event) { - const Vector<HTMLElement*>& listItems = this->listItems(); + auto& listItems = this->listItems(); - if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { focus(); + // Calling focus() may remove or change our renderer, in which case we don't want to handle the event further. - if (!renderer() || !renderer()->isListBox()) + auto* renderer = this->renderer(); + if (!is<RenderListBox>(renderer)) return; + auto& renderListBox = downcast<RenderListBox>(*renderer); // Convert to coords relative to the list box if needed. - MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); - IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms)); - int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset)); + MouseEvent& mouseEvent = downcast<MouseEvent>(event); + IntPoint localOffset = roundedIntPoint(renderListBox.absoluteToLocal(mouseEvent.absoluteLocation(), UseTransforms)); + int listIndex = renderListBox.listIndexAtOffset(toIntSize(localOffset)); if (listIndex >= 0) { if (!isDisabledFormControl()) { -#if PLATFORM(MAC) - updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey()); +#if PLATFORM(COCOA) + updateSelectedState(listIndex, mouseEvent.metaKey(), mouseEvent.shiftKey()); #else - updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey()); + updateSelectedState(listIndex, mouseEvent.ctrlKey(), mouseEvent.shiftKey()); #endif } if (Frame* frame = document().frame()) frame->eventHandler().setMouseDownMayStartAutoscroll(); - event->setDefaultHandled(); + mouseEvent.setDefaultHandled(); } - } else if (event->type() == eventNames().mousemoveEvent && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) { - MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); - if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown()) + } else if (event.type() == eventNames().mousemoveEvent && is<MouseEvent>(event) && !downcast<RenderListBox>(*renderer()).canBeScrolledAndHasScrollableArea()) { + MouseEvent& mouseEvent = downcast<MouseEvent>(event); + if (mouseEvent.button() != LeftButton || !mouseEvent.buttonDown()) return; - IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms)); - int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset)); + auto& renderListBox = downcast<RenderListBox>(*renderer()); + IntPoint localOffset = roundedIntPoint(renderListBox.absoluteToLocal(mouseEvent.absoluteLocation(), UseTransforms)); + int listIndex = renderListBox.listIndexAtOffset(toIntSize(localOffset)); if (listIndex >= 0) { if (!isDisabledFormControl()) { if (m_multiple) { @@ -1386,9 +1374,9 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event) updateListBoxSelection(true); } } - event->setDefaultHandled(); + mouseEvent.setDefaultHandled(); } - } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && document().frame()->eventHandler().autoscrollRenderer() != renderer()) { + } else if (event.type() == eventNames().mouseupEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton && document().frame()->eventHandler().autoscrollRenderer() != renderer()) { // This click or drag event was not over any of the options. if (m_lastOnChangeSelection.isEmpty()) return; @@ -1396,10 +1384,12 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event) // click. For drag selection, onChange will fire when the autoscroll // timer stops. listBoxOnChange(); - } else if (event->type() == eventNames().keydownEvent) { - if (!event->isKeyboardEvent()) + } else if (event.type() == eventNames().keydownEvent) { + if (!is<KeyboardEvent>(event)) return; - const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); + + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + const String& keyIdentifier = keyboardEvent.keyIdentifier(); bool handled = false; int endIndex = 0; @@ -1458,58 +1448,60 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event) ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size()); setActiveSelectionEndIndex(endIndex); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) m_allowsNonContiguousSelection = m_multiple && isSpatialNavigationEnabled(document().frame()); #else - m_allowsNonContiguousSelection = m_multiple && (isSpatialNavigationEnabled(document().frame()) || static_cast<KeyboardEvent*>(event)->ctrlKey()); + m_allowsNonContiguousSelection = m_multiple && (isSpatialNavigationEnabled(document().frame()) || keyboardEvent.ctrlKey()); #endif - bool selectNewItem = static_cast<KeyboardEvent*>(event)->shiftKey() || !m_allowsNonContiguousSelection; + bool selectNewItem = keyboardEvent.shiftKey() || !m_allowsNonContiguousSelection; if (selectNewItem) m_activeSelectionState = true; // If the anchor is unitialized, or if we're going to deselect all // other options, then set the anchor index equal to the end index. - bool deselectOthers = !m_multiple || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem); + bool deselectOthers = !m_multiple || (!keyboardEvent.shiftKey() && selectNewItem); if (m_activeSelectionAnchorIndex < 0 || deselectOthers) { if (deselectOthers) deselectItemsWithoutValidation(); setActiveSelectionAnchorIndex(m_activeSelectionEndIndex); } - toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex); + downcast<RenderListBox>(*renderer()).scrollToRevealElementAtListIndex(endIndex); if (selectNewItem) { updateListBoxSelection(deselectOthers); listBoxOnChange(); } else scrollToSelection(); - event->setDefaultHandled(); + keyboardEvent.setDefaultHandled(); } - } else if (event->type() == eventNames().keypressEvent) { - if (!event->isKeyboardEvent()) + } else if (event.type() == eventNames().keypressEvent) { + if (!is<KeyboardEvent>(event)) return; - int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + int keyCode = keyboardEvent.keyCode(); if (keyCode == '\r') { if (form()) - form()->submitImplicitly(event, false); - event->setDefaultHandled(); + form()->submitImplicitly(keyboardEvent, false); + keyboardEvent.setDefaultHandled(); } else if (m_multiple && keyCode == ' ' && m_allowsNonContiguousSelection) { // Use space to toggle selection change. m_activeSelectionState = !m_activeSelectionState; - ASSERT(m_activeSelectionEndIndex >= 0 - && m_activeSelectionEndIndex < static_cast<int>(listItems.size()) - && listItems[m_activeSelectionEndIndex]->hasTagName(optionTag)); + ASSERT(m_activeSelectionEndIndex >= 0); + ASSERT(m_activeSelectionEndIndex < static_cast<int>(listItems.size())); + ASSERT(is<HTMLOptionElement>(*listItems[m_activeSelectionEndIndex])); updateSelectedState(m_activeSelectionEndIndex, true /*multi*/, false /*shift*/); listBoxOnChange(); - event->setDefaultHandled(); + keyboardEvent.setDefaultHandled(); } } } -void HTMLSelectElement::defaultEventHandler(Event* event) +void HTMLSelectElement::defaultEventHandler(Event& event) { - if (!renderer()) + auto* renderer = this->renderer(); + if (!renderer) return; #if !PLATFORM(IOS) @@ -1518,21 +1510,21 @@ void HTMLSelectElement::defaultEventHandler(Event* event) return; } - if (renderer()->isMenuList()) + if (renderer->isMenuList()) menuListDefaultEventHandler(event); else listBoxDefaultEventHandler(event); #else menuListDefaultEventHandler(event); #endif - if (event->defaultHandled()) + if (event.defaultHandled()) return; - if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) { - KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event); - if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && u_isprint(keyboardEvent->charCode())) { + if (event.type() == eventNames().keypressEvent && is<KeyboardEvent>(event)) { + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + if (!keyboardEvent.ctrlKey() && !keyboardEvent.altKey() && !keyboardEvent.metaKey() && u_isprint(keyboardEvent.charCode())) { typeAheadFind(keyboardEvent); - event->setDefaultHandled(); + event.setDefaultHandled(); return; } } @@ -1541,10 +1533,10 @@ void HTMLSelectElement::defaultEventHandler(Event* event) int HTMLSelectElement::lastSelectedListIndex() const { - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); for (size_t i = items.size(); i;) { - HTMLElement* element = items[--i]; - if (isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected()) + auto& element = *items[--i]; + if (is<HTMLOptionElement>(element) && downcast<HTMLOptionElement>(element).selected()) return i; } return -1; @@ -1562,17 +1554,15 @@ int HTMLSelectElement::optionCount() const String HTMLSelectElement::optionAtIndex(int index) const { - const Vector<HTMLElement*>& items = listItems(); - - HTMLElement* element = items[index]; - if (!isHTMLOptionElement(element) || toHTMLOptionElement(element)->isDisabledFormControl()) + auto& element = *listItems()[index]; + if (!is<HTMLOptionElement>(element) || downcast<HTMLOptionElement>(element).isDisabledFormControl()) return String(); - return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); + return downcast<HTMLOptionElement>(element).textIndentedToRespectGroupLabel(); } -void HTMLSelectElement::typeAheadFind(KeyboardEvent* event) +void HTMLSelectElement::typeAheadFind(KeyboardEvent& event) { - int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar); + int index = m_typeAhead.handleEvent(&event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar); if (index < 0) return; selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven); @@ -1586,8 +1576,7 @@ Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode // items yet - but for innerHTML and related methods, this method is called // after the whole subtree is constructed. recalcListItems(); - HTMLFormControlElementWithState::insertedInto(insertionPoint); - return InsertionDone; + return HTMLFormControlElementWithState::insertedInto(insertionPoint); } void HTMLSelectElement::accessKeySetSelectedIndex(int index) @@ -1597,13 +1586,13 @@ void HTMLSelectElement::accessKeySetSelectedIndex(int index) accessKeyAction(false); // If this index is already selected, unselect. otherwise update the selected index. - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); int listIndex = optionToListIndex(index); if (listIndex >= 0) { - HTMLElement* element = items[listIndex]; - if (isHTMLOptionElement(element)) { - if (toHTMLOptionElement(element)->selected()) - toHTMLOptionElement(element)->setSelectedState(false); + auto& element = *items[listIndex]; + if (is<HTMLOptionElement>(element)) { + if (downcast<HTMLOptionElement>(element).selected()) + downcast<HTMLOptionElement>(element).setSelectedState(false); else selectOption(index, DispatchChangeEvent | UserDriven); } @@ -1621,9 +1610,9 @@ unsigned HTMLSelectElement::length() const { unsigned options = 0; - const Vector<HTMLElement*>& items = listItems(); + auto& items = listItems(); for (unsigned i = 0; i < items.size(); ++i) { - if (isHTMLOptionElement(items[i])) + if (is<HTMLOptionElement>(*items[i])) ++options; } |