2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
3 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
4 * (C) 1999 Antti Koivisto (koivisto@kde.org)
5 * (C) 2001 Dirk Mueller (mueller@kde.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights reserved.
7 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
8 * Copyright (C) 2010 Google Inc. All rights reserved.
9 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB. If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
29 #include "HTMLSelectElement.h"
31 #include "AXObjectCache.h"
33 #include "ChromeClient.h"
34 #include "ElementTraversal.h"
35 #include "EventHandler.h"
36 #include "EventNames.h"
37 #include "ExceptionCodePlaceholder.h"
38 #include "FormController.h"
39 #include "FormDataList.h"
41 #include "GenericCachedHTMLCollection.h"
42 #include "HTMLFormElement.h"
43 #include "HTMLNames.h"
44 #include "HTMLOptGroupElement.h"
45 #include "HTMLOptionElement.h"
46 #include "HTMLOptionsCollection.h"
47 #include "KeyboardEvent.h"
48 #include "LocalizedStrings.h"
49 #include "MouseEvent.h"
50 #include "NodeRareData.h"
52 #include "PlatformMouseEvent.h"
53 #include "RenderListBox.h"
54 #include "RenderMenuList.h"
55 #include "RenderTheme.h"
57 #include "SpatialNavigation.h"
59 using namespace WTF::Unicode;
63 using namespace HTMLNames;
65 // Upper limit agreed upon with representatives of Opera and Mozilla.
66 static const unsigned maxSelectItems = 10000;
68 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
69 : HTMLFormControlElementWithState(tagName, document, form)
72 , m_lastOnChangeIndex(-1)
73 , m_activeSelectionAnchorIndex(-1)
74 , m_activeSelectionEndIndex(-1)
75 , m_isProcessingUserDrivenChange(false)
77 , m_activeSelectionState(false)
78 , m_allowsNonContiguousSelection(false)
79 , m_shouldRecalcListItems(false)
81 ASSERT(hasTagName(selectTag));
84 Ref<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
86 ASSERT(tagName.matches(selectTag));
87 return adoptRef(*new HTMLSelectElement(tagName, document, form));
90 void HTMLSelectElement::didRecalcStyle(Style::Change styleChange)
92 // Even though the options didn't necessarily change, we will call setOptionsChangedOnRenderer for its side effect
93 // of recomputing the width of the element. We need to do that if the style change included a change in zoom level.
94 setOptionsChangedOnRenderer();
95 HTMLFormControlElement::didRecalcStyle(styleChange);
98 const AtomicString& HTMLSelectElement::formControlType() const
100 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
101 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
102 return m_multiple ? selectMultiple : selectOne;
105 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
107 deselectItemsWithoutValidation(excludeElement);
111 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
113 // User interaction such as mousedown events can cause list box select elements to send change events.
114 // This produces that same behavior for changes triggered by other code running on behalf of the user.
115 if (!usesMenuList()) {
116 updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
123 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
124 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
125 // The selectOption function does not behave this way, possibly because other callers need a change event even
126 // in cases where the selected option is not change.
127 if (optionIndex == selectedIndex())
130 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
133 bool HTMLSelectElement::hasPlaceholderLabelOption() const
135 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
137 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
138 // Using "size() > 1" here because size() may be 0 in WebKit.
139 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
141 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
142 // In this case, the display size should be assumed as the default.
143 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
145 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
146 if (multiple() || size() > 1)
149 int listIndex = optionToListIndex(0);
150 ASSERT(listIndex >= 0);
153 HTMLOptionElement& option = downcast<HTMLOptionElement>(*listItems()[listIndex]);
154 return !listIndex && option.value().isEmpty();
157 String HTMLSelectElement::validationMessage() const
163 return customValidationMessage();
165 return valueMissing() ? validationMessageValueMissingForSelectText() : String();
168 bool HTMLSelectElement::valueMissing() const
176 int firstSelectionIndex = selectedIndex();
178 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
179 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
182 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
185 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
187 updateSelectedState(listIndex, allowMultiplySelections, shift);
194 bool HTMLSelectElement::usesMenuList() const
197 const Page* page = document().page();
198 RefPtr<RenderTheme> renderTheme = page ? &page->theme() : RenderTheme::defaultTheme();
199 if (renderTheme->delegatesMenuListRendering())
202 return !m_multiple && m_size <= 1;
208 int HTMLSelectElement::activeSelectionStartListIndex() const
210 if (m_activeSelectionAnchorIndex >= 0)
211 return m_activeSelectionAnchorIndex;
212 return optionToListIndex(selectedIndex());
215 int HTMLSelectElement::activeSelectionEndListIndex() const
217 if (m_activeSelectionEndIndex >= 0)
218 return m_activeSelectionEndIndex;
219 return lastSelectedListIndex();
222 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* beforeElement, ExceptionCode& ec)
224 if (!element || !(is<HTMLOptionElement>(*element) || element->hasTagName(hrTag) || is<HTMLOptGroupElement>(*element)))
227 // Make sure the element is ref'd and deref'd so we don't leak it.
228 Ref<HTMLElement> protectNewChild(*element);
230 insertBefore(*element, beforeElement, ec);
234 void HTMLSelectElement::add(HTMLElement* element, int beforeIndex, ExceptionCode& ec)
236 add(element, item(beforeIndex), ec);
239 void HTMLSelectElement::removeByIndex(int optionIndex)
241 int listIndex = optionToListIndex(optionIndex);
245 listItems()[listIndex]->remove(IGNORE_EXCEPTION);
248 void HTMLSelectElement::remove(HTMLOptionElement* option)
250 if (option->ownerSelectElement() != this)
253 option->remove(IGNORE_EXCEPTION);
256 String HTMLSelectElement::value() const
258 for (auto* item : listItems()) {
259 if (is<HTMLOptionElement>(*item)) {
260 HTMLOptionElement& option = downcast<HTMLOptionElement>(*item);
261 if (option.selected())
262 return option.value();
265 return emptyString();
268 void HTMLSelectElement::setValue(const String &value)
270 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
271 if (value.isNull()) {
272 setSelectedIndex(-1);
276 // Find the option with value() matching the given parameter and make it the current selection.
277 unsigned optionIndex = 0;
278 for (auto* item : listItems()) {
279 if (is<HTMLOptionElement>(*item)) {
280 if (downcast<HTMLOptionElement>(*item).value() == value) {
281 setSelectedIndex(optionIndex);
288 setSelectedIndex(-1);
291 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
293 if (name == alignAttr) {
294 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
295 // See http://bugs.webkit.org/show_bug.cgi?id=12072
299 return HTMLFormControlElementWithState::isPresentationAttribute(name);
302 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
304 if (name == sizeAttr) {
305 int oldSize = m_size;
306 // Set the attribute value to a number.
307 // This is important since the style rules for this attribute can determine the appearance property.
308 int size = value.toInt();
309 AtomicString attrSize = AtomicString::number(size);
310 if (attrSize != value) {
311 // FIXME: This is horribly factored.
312 if (Attribute* sizeAttribute = ensureUniqueElementData().findAttributeByName(sizeAttr))
313 sizeAttribute->setValue(attrSize);
315 size = std::max(size, 0);
317 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
319 updateListItemSelectedStates();
323 if (m_size != oldSize) {
324 setNeedsStyleRecalc(ReconstructRenderTree);
325 setRecalcListItems();
327 } else if (name == multipleAttr)
328 parseMultipleAttribute(value);
329 else if (name == accesskeyAttr) {
330 // FIXME: ignore for the moment.
333 HTMLFormControlElementWithState::parseAttribute(name, value);
336 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
339 return isFocusable();
340 return HTMLFormControlElementWithState::isKeyboardFocusable(event);
343 bool HTMLSelectElement::isMouseFocusable() const
346 return isFocusable();
347 return HTMLFormControlElementWithState::isMouseFocusable();
350 bool HTMLSelectElement::canSelectAll() const
352 return !usesMenuList();
355 RenderPtr<RenderElement> HTMLSelectElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&)
359 return createRenderer<RenderMenuList>(*this, WTF::move(style));
360 return createRenderer<RenderListBox>(*this, WTF::move(style));
362 return createRenderer<RenderMenuList>(*this, WTF::move(style));
366 bool HTMLSelectElement::childShouldCreateRenderer(const Node& child) const
368 if (!HTMLFormControlElementWithState::childShouldCreateRenderer(child))
372 return is<HTMLOptionElement>(child) || is<HTMLOptGroupElement>(child) || validationMessageShadowTreeContains(child);
374 return validationMessageShadowTreeContains(child);
377 Ref<HTMLCollection> HTMLSelectElement::selectedOptions()
379 return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<SelectedOptions>::traversalType>>(*this, SelectedOptions);
382 Ref<HTMLOptionsCollection> HTMLSelectElement::options()
384 return ensureRareData().ensureNodeLists().addCachedCollection<HTMLOptionsCollection>(*this, SelectOptions);
387 void HTMLSelectElement::updateListItemSelectedStates()
389 if (m_shouldRecalcListItems)
393 void HTMLSelectElement::childrenChanged(const ChildChange& change)
395 setRecalcListItems();
397 m_lastOnChangeSelection.clear();
399 HTMLFormControlElementWithState::childrenChanged(change);
402 void HTMLSelectElement::optionElementChildrenChanged()
404 setRecalcListItems();
408 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
409 cache->childrenChanged(this);
413 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
416 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
419 void HTMLSelectElement::setMultiple(bool multiple)
421 bool oldMultiple = this->multiple();
422 int oldSelectedIndex = selectedIndex();
423 setAttribute(multipleAttr, multiple ? "" : 0);
425 // Restore selectedIndex after changing the multiple flag to preserve
426 // selection as single-line and multi-line has different defaults.
427 if (oldMultiple != this->multiple())
428 setSelectedIndex(oldSelectedIndex);
431 void HTMLSelectElement::setSize(int size)
433 setIntegralAttribute(sizeAttr, size);
436 HTMLOptionElement* HTMLSelectElement::namedItem(const AtomicString& name)
438 return downcast<HTMLOptionElement>(options()->namedItem(name));
441 HTMLOptionElement* HTMLSelectElement::item(unsigned index)
443 return downcast<HTMLOptionElement>(options()->item(index));
446 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
449 if (index > maxSelectItems - 1)
450 index = maxSelectItems - 1;
451 int diff = index - length();
452 RefPtr<HTMLElement> before = 0;
453 // Out of array bounds? First insert empty dummies.
455 setLength(index, ec);
456 // Replace an existing entry?
457 } else if (diff < 0) {
458 before = downcast<HTMLElement>(options()->item(index + 1));
459 removeByIndex(index);
461 // Finally add the new element.
463 add(option, before.get(), ec);
464 if (diff >= 0 && option->selected())
465 optionSelectionStateChanged(option, true);
469 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
472 if (newLen > maxSelectItems)
473 newLen = maxSelectItems;
474 int diff = length() - newLen;
476 if (diff < 0) { // Add dummy elements.
478 RefPtr<Element> option = document().createElement(optionTag, false);
480 add(downcast<HTMLElement>(option.get()), nullptr, ec);
485 const Vector<HTMLElement*>& items = listItems();
487 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
488 // of elements that we intend to remove then attempt to remove them one at a time.
489 Vector<Ref<Element>> itemsToRemove;
490 size_t optionIndex = 0;
491 for (auto& item : items) {
492 if (is<HTMLOptionElement>(*item) && optionIndex++ >= newLen) {
493 ASSERT(item->parentNode());
494 itemsToRemove.append(*item);
498 for (auto& item : itemsToRemove) {
499 if (item->parentNode())
500 item->parentNode()->removeChild(item.get(), ec);
506 bool HTMLSelectElement::isRequiredFormControl() const
512 bool HTMLSelectElement::willRespondToMouseClickEvents()
514 return !isDisabledFormControl();
518 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
519 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
520 // Otherwise, it returns |listIndex|.
521 // Valid means that it is enabled and an option element.
522 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
524 ASSERT(direction == -1 || direction == 1);
525 const Vector<HTMLElement*>& listItems = this->listItems();
526 int lastGoodIndex = listIndex;
527 int size = listItems.size();
528 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
530 if (!listItems[listIndex]->isDisabledFormControl() && is<HTMLOptionElement>(*listItems[listIndex])) {
531 lastGoodIndex = listIndex;
536 return lastGoodIndex;
539 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
541 return nextValidIndex(startIndex, SkipForwards, 1);
544 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
546 if (startIndex == -1)
547 startIndex = listItems().size();
548 return nextValidIndex(startIndex, SkipBackwards, 1);
551 int HTMLSelectElement::firstSelectableListIndex() const
553 const Vector<HTMLElement*>& items = listItems();
554 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
555 if (static_cast<size_t>(index) == items.size())
560 int HTMLSelectElement::lastSelectableListIndex() const
562 return nextValidIndex(-1, SkipForwards, INT_MAX);
565 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
566 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
568 const Vector<HTMLElement*>& items = listItems();
569 // Can't use m_size because renderer forces a minimum size.
571 if (is<RenderListBox>(*renderer()))
572 pageSize = downcast<RenderListBox>(*renderer()).size() - 1; // -1 so we still show context.
574 // One page away, but not outside valid bounds.
575 // If there is a valid option item one page away, the index is chosen.
576 // If there is no exact one page away valid option, returns startIndex or the most far index.
577 int edgeIndex = direction == SkipForwards ? 0 : items.size() - 1;
578 int skipAmount = pageSize + (direction == SkipForwards ? startIndex : edgeIndex - startIndex);
579 return nextValidIndex(edgeIndex, direction, skipAmount);
582 void HTMLSelectElement::selectAll()
584 ASSERT(!usesMenuList());
585 if (!renderer() || !m_multiple)
588 // Save the selection so it can be compared to the new selectAll selection
589 // when dispatching change events.
592 m_activeSelectionState = true;
593 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
594 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
595 if (m_activeSelectionAnchorIndex < 0)
598 updateListBoxSelection(false);
603 void HTMLSelectElement::saveLastSelection()
605 if (usesMenuList()) {
606 m_lastOnChangeIndex = selectedIndex();
610 m_lastOnChangeSelection.clear();
611 const Vector<HTMLElement*>& items = listItems();
612 for (unsigned i = 0; i < items.size(); ++i) {
613 HTMLElement* element = items[i];
614 m_lastOnChangeSelection.append(is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected());
618 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
620 m_activeSelectionAnchorIndex = index;
622 // Cache the selection state so we can restore the old selection as the new
623 // selection pivots around this anchor index.
624 m_cachedStateForActiveSelection.clear();
626 const Vector<HTMLElement*>& items = listItems();
627 for (unsigned i = 0; i < items.size(); ++i) {
628 HTMLElement* element = items[i];
629 m_cachedStateForActiveSelection.append(is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected());
633 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
635 m_activeSelectionEndIndex = index;
638 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
641 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
643 ASSERT(renderer() && (renderer()->isMenuList() || m_multiple));
645 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
647 unsigned start = std::min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
648 unsigned end = std::max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
650 const Vector<HTMLElement*>& items = listItems();
651 for (unsigned i = 0; i < items.size(); ++i) {
652 HTMLElement* element = items[i];
653 if (!is<HTMLOptionElement>(*element) || downcast<HTMLOptionElement>(*element).isDisabledFormControl())
656 if (i >= start && i <= end)
657 downcast<HTMLOptionElement>(*element).setSelectedState(m_activeSelectionState);
658 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
659 downcast<HTMLOptionElement>(*element).setSelectedState(false);
661 downcast<HTMLOptionElement>(*element).setSelectedState(m_cachedStateForActiveSelection[i]);
668 void HTMLSelectElement::listBoxOnChange()
670 ASSERT(!usesMenuList() || m_multiple);
672 const Vector<HTMLElement*>& items = listItems();
674 // If the cached selection list is empty, or the size has changed, then fire
675 // dispatchFormControlChangeEvent, and return early.
676 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
677 dispatchFormControlChangeEvent();
681 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
682 bool fireOnChange = false;
683 for (unsigned i = 0; i < items.size(); ++i) {
684 HTMLElement* element = items[i];
685 bool selected = is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected();
686 if (selected != m_lastOnChangeSelection[i])
688 m_lastOnChangeSelection[i] = selected;
692 dispatchInputEvent();
693 dispatchFormControlChangeEvent();
697 void HTMLSelectElement::dispatchChangeEventForMenuList()
699 ASSERT(usesMenuList());
701 int selected = selectedIndex();
702 if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
703 m_lastOnChangeIndex = selected;
704 m_isProcessingUserDrivenChange = false;
705 dispatchInputEvent();
706 dispatchFormControlChangeEvent();
710 void HTMLSelectElement::scrollToSelection()
716 auto* renderer = this->renderer();
717 if (!is<RenderListBox>(renderer))
719 downcast<RenderListBox>(*renderer).selectionChanged();
721 if (auto* renderer = this->renderer())
726 void HTMLSelectElement::setOptionsChangedOnRenderer()
728 if (auto* renderer = this->renderer()) {
730 if (is<RenderMenuList>(*renderer))
731 downcast<RenderMenuList>(*renderer).setOptionsChanged(true);
733 downcast<RenderListBox>(*renderer).setOptionsChanged(true);
735 downcast<RenderMenuList>(*renderer).setOptionsChanged(true);
740 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
742 if (m_shouldRecalcListItems)
746 Vector<HTMLElement*> items = m_listItems;
747 recalcListItems(false);
748 ASSERT(items == m_listItems);
755 void HTMLSelectElement::invalidateSelectedItems()
757 if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
758 collection->invalidateCache(document());
761 void HTMLSelectElement::setRecalcListItems()
763 m_shouldRecalcListItems = true;
764 // Manual selection anchor is reset when manipulating the select programmatically.
765 m_activeSelectionAnchorIndex = -1;
766 setOptionsChangedOnRenderer();
767 setNeedsStyleRecalc();
769 if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
770 collection->invalidateCache(document());
773 invalidateSelectedItems();
776 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
777 cache->childrenChanged(this);
781 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
785 m_shouldRecalcListItems = false;
787 HTMLOptionElement* foundSelected = 0;
788 HTMLOptionElement* firstOption = 0;
789 for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
790 if (!is<HTMLElement>(*currentElement)) {
791 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
794 HTMLElement& current = downcast<HTMLElement>(*currentElement);
796 // optgroup tags may not nest. However, both FireFox and IE will
797 // flatten the tree automatically, so we follow suit.
798 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
799 if (is<HTMLOptGroupElement>(current)) {
800 m_listItems.append(¤t);
801 if (Element* nextElement = ElementTraversal::firstWithin(current)) {
802 currentElement = nextElement;
807 if (is<HTMLOptionElement>(current)) {
808 m_listItems.append(¤t);
810 if (updateSelectedStates && !m_multiple) {
811 HTMLOptionElement& option = downcast<HTMLOptionElement>(current);
813 firstOption = &option;
814 if (option.selected()) {
816 foundSelected->setSelectedState(false);
817 foundSelected = &option;
818 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
819 foundSelected = &option;
820 foundSelected->setSelectedState(true);
825 if (current.hasTagName(hrTag))
826 m_listItems.append(¤t);
828 // In conforming HTML code, only <optgroup> and <option> will be found
829 // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
830 // into those tags that we choose to. For web-compat, we should cope
831 // with the case where odd tags like a <div> have been added but we
832 // handle this because such tags have already been removed from the
833 // <select>'s subtree at this point.
834 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
837 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
838 firstOption->setSelectedState(true);
841 int HTMLSelectElement::selectedIndex() const
845 // Return the number of the first option selected.
846 const Vector<HTMLElement*>& items = listItems();
847 for (size_t i = 0; i < items.size(); ++i) {
848 HTMLElement* element = items[i];
849 if (is<HTMLOptionElement>(*element)) {
850 if (downcast<HTMLOptionElement>(*element).selected())
859 void HTMLSelectElement::setSelectedIndex(int index)
861 selectOption(index, DeselectOtherOptions);
864 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
866 ASSERT(option->ownerSelectElement() == this);
867 if (optionIsSelected)
868 selectOption(option->index());
869 else if (!usesMenuList())
872 selectOption(nextSelectableListIndex(-1));
875 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
877 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
879 const Vector<HTMLElement*>& items = listItems();
880 int listIndex = optionToListIndex(optionIndex);
882 HTMLElement* element = nullptr;
883 if (listIndex >= 0) {
884 element = items[listIndex];
888 deselectItemsWithoutValidation(element);
890 if (is<HTMLOptionElement>(element)) {
891 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
892 setActiveSelectionAnchorIndex(listIndex);
893 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
894 setActiveSelectionEndIndex(listIndex);
895 downcast<HTMLOptionElement>(*element).setSelectedState(true);
898 // For the menu list case, this is what makes the selected element appear.
899 if (auto* renderer = this->renderer())
900 renderer->updateFromElement();
905 if (usesMenuList()) {
906 m_isProcessingUserDrivenChange = flags & UserDriven;
907 if (flags & DispatchChangeEvent)
908 dispatchChangeEventForMenuList();
909 if (auto* renderer = this->renderer()) {
910 if (is<RenderMenuList>(*renderer))
911 downcast<RenderMenuList>(*renderer).didSetSelectedIndex(listIndex);
913 downcast<RenderListBox>(*renderer).selectionChanged();
918 int HTMLSelectElement::optionToListIndex(int optionIndex) const
920 const Vector<HTMLElement*>& items = listItems();
921 int listSize = static_cast<int>(items.size());
922 if (optionIndex < 0 || optionIndex >= listSize)
925 int optionIndex2 = -1;
926 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
927 if (is<HTMLOptionElement>(*items[listIndex])) {
929 if (optionIndex2 == optionIndex)
937 int HTMLSelectElement::listToOptionIndex(int listIndex) const
939 const Vector<HTMLElement*>& items = listItems();
940 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !is<HTMLOptionElement>(*items[listIndex]))
943 // Actual index of option not counting OPTGROUP entries that may be in list.
945 for (int i = 0; i < listIndex; ++i) {
946 if (is<HTMLOptionElement>(*items[i]))
953 void HTMLSelectElement::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection direction)
955 // Save the selection so it can be compared to the new selection when
956 // dispatching change events during blur event dispatch.
959 HTMLFormControlElementWithState::dispatchFocusEvent(WTF::move(oldFocusedElement), direction);
962 void HTMLSelectElement::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement)
964 // We only need to fire change events here for menu lists, because we fire
965 // change events for list boxes whenever the selection change is actually made.
966 // This matches other browsers' behavior.
968 dispatchChangeEventForMenuList();
969 HTMLFormControlElementWithState::dispatchBlurEvent(WTF::move(newFocusedElement));
972 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
974 const Vector<HTMLElement*>& items = listItems();
975 for (unsigned i = 0; i < items.size(); ++i) {
976 HTMLElement* element = items[i];
977 if (element != excludeElement && is<HTMLOptionElement>(*element))
978 downcast<HTMLOptionElement>(*element).setSelectedState(false);
982 FormControlState HTMLSelectElement::saveFormControlState() const
984 const Vector<HTMLElement*>& items = listItems();
985 size_t length = items.size();
986 FormControlState state;
987 for (unsigned i = 0; i < length; ++i) {
988 if (!is<HTMLOptionElement>(*items[i]))
990 HTMLOptionElement& option = downcast<HTMLOptionElement>(*items[i]);
991 if (!option.selected())
993 state.append(option.value());
1000 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
1002 const Vector<HTMLElement*>& items = listItems();
1003 size_t loopEndIndex = std::min(items.size(), listIndexEnd);
1004 for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
1005 if (!is<HTMLOptionElement>(*items[i]))
1007 if (downcast<HTMLOptionElement>(*items[i]).value() == value)
1013 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
1017 const Vector<HTMLElement*>& items = listItems();
1018 size_t itemsSize = items.size();
1022 for (size_t i = 0; i < itemsSize; ++i) {
1023 if (!is<HTMLOptionElement>(*items[i]))
1025 downcast<HTMLOptionElement>(*items[i]).setSelectedState(false);
1029 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
1030 if (foundIndex != notFound)
1031 downcast<HTMLOptionElement>(*items[foundIndex]).setSelectedState(true);
1033 size_t startIndex = 0;
1034 for (size_t i = 0; i < state.valueSize(); ++i) {
1035 const String& value = state[i];
1036 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
1037 if (foundIndex == notFound)
1038 foundIndex = searchOptionsForValue(value, 0, startIndex);
1039 if (foundIndex == notFound)
1041 downcast<HTMLOptionElement>(*items[foundIndex]).setSelectedState(true);
1042 startIndex = foundIndex + 1;
1046 setOptionsChangedOnRenderer();
1050 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
1052 bool oldUsesMenuList = usesMenuList();
1053 m_multiple = !value.isNull();
1055 if (oldUsesMenuList != usesMenuList())
1056 setNeedsStyleRecalc(ReconstructRenderTree);
1059 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
1061 const AtomicString& name = this->name();
1065 bool successful = false;
1066 const Vector<HTMLElement*>& items = listItems();
1068 for (unsigned i = 0; i < items.size(); ++i) {
1069 HTMLElement* element = items[i];
1070 if (is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected() && !downcast<HTMLOptionElement>(*element).isDisabledFormControl()) {
1071 list.appendData(name, downcast<HTMLOptionElement>(*element).value());
1076 // It's possible that this is a menulist with multiple options and nothing
1077 // will be submitted (!successful). We won't send a unselected non-disabled
1078 // option as fallback. This behavior matches to other browsers.
1082 void HTMLSelectElement::reset()
1084 HTMLOptionElement* firstOption = nullptr;
1085 HTMLOptionElement* selectedOption = nullptr;
1087 const Vector<HTMLElement*>& items = listItems();
1088 for (unsigned i = 0; i < items.size(); ++i) {
1089 HTMLElement* element = items[i];
1090 if (!is<HTMLOptionElement>(*element))
1093 HTMLOptionElement& option = downcast<HTMLOptionElement>(*element);
1094 if (option.fastHasAttribute(selectedAttr)) {
1095 if (selectedOption && !m_multiple)
1096 selectedOption->setSelectedState(false);
1097 option.setSelectedState(true);
1098 selectedOption = &option;
1100 option.setSelectedState(false);
1103 firstOption = &option;
1106 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
1107 firstOption->setSelectedState(true);
1109 setOptionsChangedOnRenderer();
1110 setNeedsStyleRecalc();
1115 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
1117 const Page* page = document().page();
1118 RefPtr<RenderTheme> renderTheme = page ? &page->theme() : RenderTheme::defaultTheme();
1120 if (!renderTheme->popsMenuByArrowKeys())
1123 if (!isSpatialNavigationEnabled(document().frame())) {
1124 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
1126 // Calling focus() may cause us to lose our renderer. Return true so
1127 // that our caller doesn't process the event further, but don't set
1128 // the event as handled.
1129 if (!is<RenderMenuList>(renderer()))
1132 // Save the selection so it can be compared to the new selection
1133 // when dispatching change events during selectOption, which
1134 // gets called from RenderMenuList::valueChanged, which gets called
1135 // after the user makes a selection from the menu.
1136 saveLastSelection();
1137 downcast<RenderMenuList>(*renderer()).showPopup();
1138 event->setDefaultHandled();
1147 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1149 ASSERT(renderer() && renderer()->isMenuList());
1151 const Page* page = document().page();
1152 RefPtr<RenderTheme> renderTheme = page ? &page->theme() : RenderTheme::defaultTheme();
1154 if (event->type() == eventNames().keydownEvent) {
1155 if (!is<KeyboardEvent>(*event))
1158 KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(*event);
1159 if (platformHandleKeydownEvent(&keyboardEvent))
1162 // When using spatial navigation, we want to be able to navigate away
1163 // from the select element when the user hits any of the arrow keys,
1164 // instead of changing the selection.
1165 if (isSpatialNavigationEnabled(document().frame())) {
1166 if (!m_activeSelectionState)
1170 const String& keyIdentifier = keyboardEvent.keyIdentifier();
1171 bool handled = true;
1172 const Vector<HTMLElement*>& listItems = this->listItems();
1173 int listIndex = optionToListIndex(selectedIndex());
1175 // When using caret browsing, we want to be able to move the focus
1176 // out of the select element when user hits a left or right arrow key.
1177 const Frame* frame = document().frame();
1178 if (frame && frame->settings().caretBrowsingEnabled()) {
1179 if (keyIdentifier == "Left" || keyIdentifier == "Right")
1183 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1184 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1185 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1186 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1187 else if (keyIdentifier == "PageDown")
1188 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1189 else if (keyIdentifier == "PageUp")
1190 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1191 else if (keyIdentifier == "Home")
1192 listIndex = nextValidIndex(-1, SkipForwards, 1);
1193 else if (keyIdentifier == "End")
1194 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1198 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1199 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1202 keyboardEvent.setDefaultHandled();
1205 // Use key press event here since sending simulated mouse events
1206 // on key down blocks the proper sending of the key press event.
1207 if (event->type() == eventNames().keypressEvent) {
1208 if (!is<KeyboardEvent>(*event))
1211 KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(*event);
1212 int keyCode = keyboardEvent.keyCode();
1213 bool handled = false;
1215 if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1216 // Use space to toggle arrow key handling for selection change or spatial navigation.
1217 m_activeSelectionState = !m_activeSelectionState;
1218 keyboardEvent.setDefaultHandled();
1222 if (renderTheme->popsMenuBySpaceOrReturn()) {
1223 if (keyCode == ' ' || keyCode == '\r') {
1226 // Calling focus() may remove the renderer or change the
1228 if (!is<RenderMenuList>(renderer()))
1231 // Save the selection so it can be compared to the new selection
1232 // when dispatching change events during selectOption, which
1233 // gets called from RenderMenuList::valueChanged, which gets called
1234 // after the user makes a selection from the menu.
1235 saveLastSelection();
1236 downcast<RenderMenuList>(*renderer()).showPopup();
1239 } else if (renderTheme->popsMenuByArrowKeys()) {
1240 if (keyCode == ' ') {
1243 // Calling focus() may remove the renderer or change the
1245 if (!is<RenderMenuList>(renderer()))
1248 // Save the selection so it can be compared to the new selection
1249 // when dispatching change events during selectOption, which
1250 // gets called from RenderMenuList::valueChanged, which gets called
1251 // after the user makes a selection from the menu.
1252 saveLastSelection();
1253 downcast<RenderMenuList>(*renderer()).showPopup();
1255 } else if (keyCode == '\r') {
1257 form()->submitImplicitly(&keyboardEvent, false);
1258 dispatchChangeEventForMenuList();
1264 keyboardEvent.setDefaultHandled();
1267 if (event->type() == eventNames().mousedownEvent && is<MouseEvent>(*event) && downcast<MouseEvent>(*event).button() == LeftButton) {
1270 if (is<RenderMenuList>(renderer())) {
1271 auto& menuList = downcast<RenderMenuList>(*renderer());
1272 ASSERT(!menuList.popupIsVisible());
1273 // Save the selection so it can be compared to the new
1274 // selection when we call onChange during selectOption,
1275 // which gets called from RenderMenuList::valueChanged,
1276 // which gets called after the user makes a selection from
1278 saveLastSelection();
1279 menuList.showPopup();
1282 event->setDefaultHandled();
1286 if (event->type() == eventNames().blurEvent && !focused()) {
1287 auto& menuList = downcast<RenderMenuList>(*renderer());
1288 if (menuList.popupIsVisible())
1289 menuList.hidePopup();
1294 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1296 ASSERT(listIndex >= 0);
1298 // Save the selection so it can be compared to the new selection when
1299 // dispatching change events during mouseup, or after autoscroll finishes.
1300 saveLastSelection();
1302 m_activeSelectionState = true;
1304 bool shiftSelect = m_multiple && shift;
1305 bool multiSelect = m_multiple && multi && !shift;
1307 HTMLElement* clickedElement = listItems()[listIndex];
1308 if (is<HTMLOptionElement>(*clickedElement)) {
1309 // Keep track of whether an active selection (like during drag
1310 // selection), should select or deselect.
1311 if (downcast<HTMLOptionElement>(*clickedElement).selected() && multiSelect)
1312 m_activeSelectionState = false;
1313 if (!m_activeSelectionState)
1314 downcast<HTMLOptionElement>(*clickedElement).setSelectedState(false);
1317 // If we're not in any special multiple selection mode, then deselect all
1318 // other items, excluding the clicked option. If no option was clicked, then
1319 // this will deselect all items in the list.
1320 if (!shiftSelect && !multiSelect)
1321 deselectItemsWithoutValidation(clickedElement);
1323 // If the anchor hasn't been set, and we're doing a single selection or a
1324 // shift selection, then initialize the anchor to the first selected index.
1325 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1326 setActiveSelectionAnchorIndex(selectedIndex());
1328 // Set the selection state of the clicked option.
1329 if (is<HTMLOptionElement>(*clickedElement) && !downcast<HTMLOptionElement>(*clickedElement).isDisabledFormControl())
1330 downcast<HTMLOptionElement>(*clickedElement).setSelectedState(true);
1332 // If there was no selectedIndex() for the previous initialization, or If
1333 // we're doing a single selection, or a multiple selection (using cmd or
1334 // ctrl), then initialize the anchor index to the listIndex that just got
1336 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1337 setActiveSelectionAnchorIndex(listIndex);
1339 setActiveSelectionEndIndex(listIndex);
1340 updateListBoxSelection(!multiSelect);
1343 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1345 const Vector<HTMLElement*>& listItems = this->listItems();
1347 if (event->type() == eventNames().mousedownEvent && is<MouseEvent>(*event) && downcast<MouseEvent>(*event).button() == LeftButton) {
1349 // Calling focus() may remove or change our renderer, in which case we don't want to handle the event further.
1350 if (!is<RenderListBox>(renderer()))
1353 // Convert to coords relative to the list box if needed.
1354 MouseEvent& mouseEvent = downcast<MouseEvent>(*event);
1355 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent.absoluteLocation(), UseTransforms));
1356 int listIndex = downcast<RenderListBox>(*renderer()).listIndexAtOffset(toIntSize(localOffset));
1357 if (listIndex >= 0) {
1358 if (!isDisabledFormControl()) {
1360 updateSelectedState(listIndex, mouseEvent.metaKey(), mouseEvent.shiftKey());
1362 updateSelectedState(listIndex, mouseEvent.ctrlKey(), mouseEvent.shiftKey());
1365 if (Frame* frame = document().frame())
1366 frame->eventHandler().setMouseDownMayStartAutoscroll();
1368 mouseEvent.setDefaultHandled();
1370 } else if (event->type() == eventNames().mousemoveEvent && is<MouseEvent>(*event) && !downcast<RenderBox>(*renderer()).canBeScrolledAndHasScrollableArea()) {
1371 MouseEvent& mouseEvent = downcast<MouseEvent>(*event);
1372 if (mouseEvent.button() != LeftButton || !mouseEvent.buttonDown())
1375 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent.absoluteLocation(), UseTransforms));
1376 int listIndex = downcast<RenderListBox>(*renderer()).listIndexAtOffset(toIntSize(localOffset));
1377 if (listIndex >= 0) {
1378 if (!isDisabledFormControl()) {
1380 // Only extend selection if there is something selected.
1381 if (m_activeSelectionAnchorIndex < 0)
1384 setActiveSelectionEndIndex(listIndex);
1385 updateListBoxSelection(false);
1387 setActiveSelectionAnchorIndex(listIndex);
1388 setActiveSelectionEndIndex(listIndex);
1389 updateListBoxSelection(true);
1392 mouseEvent.setDefaultHandled();
1394 } else if (event->type() == eventNames().mouseupEvent && is<MouseEvent>(*event) && downcast<MouseEvent>(*event).button() == LeftButton && document().frame()->eventHandler().autoscrollRenderer() != renderer()) {
1395 // This click or drag event was not over any of the options.
1396 if (m_lastOnChangeSelection.isEmpty())
1398 // This makes sure we fire dispatchFormControlChangeEvent for a single
1399 // click. For drag selection, onChange will fire when the autoscroll
1402 } else if (event->type() == eventNames().keydownEvent) {
1403 if (!is<KeyboardEvent>(*event))
1406 KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(*event);
1407 const String& keyIdentifier = keyboardEvent.keyIdentifier();
1409 bool handled = false;
1411 if (m_activeSelectionEndIndex < 0) {
1412 // Initialize the end index
1413 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1414 int startIndex = lastSelectedListIndex();
1416 if (keyIdentifier == "Down")
1417 endIndex = nextSelectableListIndex(startIndex);
1419 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1420 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1421 int startIndex = optionToListIndex(selectedIndex());
1423 if (keyIdentifier == "Up")
1424 endIndex = previousSelectableListIndex(startIndex);
1426 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1429 // Set the end index based on the current end index.
1430 if (keyIdentifier == "Down") {
1431 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1433 } else if (keyIdentifier == "Up") {
1434 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1436 } else if (keyIdentifier == "PageDown") {
1437 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1439 } else if (keyIdentifier == "PageUp") {
1440 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1444 if (keyIdentifier == "Home") {
1445 endIndex = firstSelectableListIndex();
1447 } else if (keyIdentifier == "End") {
1448 endIndex = lastSelectableListIndex();
1452 if (isSpatialNavigationEnabled(document().frame()))
1453 // Check if the selection moves to the boundary.
1454 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1457 if (endIndex >= 0 && handled) {
1458 // Save the selection so it can be compared to the new selection
1459 // when dispatching change events immediately after making the new
1461 saveLastSelection();
1463 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1464 setActiveSelectionEndIndex(endIndex);
1467 m_allowsNonContiguousSelection = m_multiple && isSpatialNavigationEnabled(document().frame());
1469 m_allowsNonContiguousSelection = m_multiple && (isSpatialNavigationEnabled(document().frame()) || keyboardEvent.ctrlKey());
1471 bool selectNewItem = keyboardEvent.shiftKey() || !m_allowsNonContiguousSelection;
1474 m_activeSelectionState = true;
1475 // If the anchor is unitialized, or if we're going to deselect all
1476 // other options, then set the anchor index equal to the end index.
1477 bool deselectOthers = !m_multiple || (!keyboardEvent.shiftKey() && selectNewItem);
1478 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1480 deselectItemsWithoutValidation();
1481 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1484 downcast<RenderListBox>(*renderer()).scrollToRevealElementAtListIndex(endIndex);
1485 if (selectNewItem) {
1486 updateListBoxSelection(deselectOthers);
1489 scrollToSelection();
1491 keyboardEvent.setDefaultHandled();
1493 } else if (event->type() == eventNames().keypressEvent) {
1494 if (!is<KeyboardEvent>(*event))
1496 KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(*event);
1497 int keyCode = keyboardEvent.keyCode();
1499 if (keyCode == '\r') {
1501 form()->submitImplicitly(&keyboardEvent, false);
1502 keyboardEvent.setDefaultHandled();
1503 } else if (m_multiple && keyCode == ' ' && m_allowsNonContiguousSelection) {
1504 // Use space to toggle selection change.
1505 m_activeSelectionState = !m_activeSelectionState;
1506 ASSERT(m_activeSelectionEndIndex >= 0);
1507 ASSERT(m_activeSelectionEndIndex < static_cast<int>(listItems.size()));
1508 ASSERT(is<HTMLOptionElement>(*listItems[m_activeSelectionEndIndex]));
1509 updateSelectedState(m_activeSelectionEndIndex, true /*multi*/, false /*shift*/);
1511 keyboardEvent.setDefaultHandled();
1516 void HTMLSelectElement::defaultEventHandler(Event* event)
1522 if (isDisabledFormControl()) {
1523 HTMLFormControlElementWithState::defaultEventHandler(event);
1527 if (renderer()->isMenuList())
1528 menuListDefaultEventHandler(event);
1530 listBoxDefaultEventHandler(event);
1532 menuListDefaultEventHandler(event);
1534 if (event->defaultHandled())
1537 if (event->type() == eventNames().keypressEvent && is<KeyboardEvent>(*event)) {
1538 KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(*event);
1539 if (!keyboardEvent.ctrlKey() && !keyboardEvent.altKey() && !keyboardEvent.metaKey() && u_isprint(keyboardEvent.charCode())) {
1540 typeAheadFind(keyboardEvent);
1541 event->setDefaultHandled();
1545 HTMLFormControlElementWithState::defaultEventHandler(event);
1548 int HTMLSelectElement::lastSelectedListIndex() const
1550 const Vector<HTMLElement*>& items = listItems();
1551 for (size_t i = items.size(); i;) {
1552 HTMLElement* element = items[--i];
1553 if (is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected())
1559 int HTMLSelectElement::indexOfSelectedOption() const
1561 return optionToListIndex(selectedIndex());
1564 int HTMLSelectElement::optionCount() const
1566 return listItems().size();
1569 String HTMLSelectElement::optionAtIndex(int index) const
1571 const Vector<HTMLElement*>& items = listItems();
1573 HTMLElement* element = items[index];
1574 if (!is<HTMLOptionElement>(*element) || downcast<HTMLOptionElement>(*element).isDisabledFormControl())
1576 return downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
1579 void HTMLSelectElement::typeAheadFind(KeyboardEvent& event)
1581 int index = m_typeAhead.handleEvent(&event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
1584 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1585 if (!usesMenuList())
1589 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode& insertionPoint)
1591 // When the element is created during document parsing, it won't have any
1592 // items yet - but for innerHTML and related methods, this method is called
1593 // after the whole subtree is constructed.
1595 return HTMLFormControlElementWithState::insertedInto(insertionPoint);
1598 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1600 // First bring into focus the list box.
1602 accessKeyAction(false);
1604 // If this index is already selected, unselect. otherwise update the selected index.
1605 const Vector<HTMLElement*>& items = listItems();
1606 int listIndex = optionToListIndex(index);
1607 if (listIndex >= 0) {
1608 HTMLElement* element = items[listIndex];
1609 if (is<HTMLOptionElement>(*element)) {
1610 if (downcast<HTMLOptionElement>(*element).selected())
1611 downcast<HTMLOptionElement>(*element).setSelectedState(false);
1613 selectOption(index, DispatchChangeEvent | UserDriven);
1618 dispatchChangeEventForMenuList();
1622 scrollToSelection();
1625 unsigned HTMLSelectElement::length() const
1627 unsigned options = 0;
1629 const Vector<HTMLElement*>& items = listItems();
1630 for (unsigned i = 0; i < items.size(); ++i) {
1631 if (is<HTMLOptionElement>(*items[i]))