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 "core/html/HTMLSelectElement.h"
31 #include "HTMLNames.h"
32 #include "bindings/v8/ExceptionMessages.h"
33 #include "bindings/v8/ExceptionState.h"
34 #include "bindings/v8/ExceptionStatePlaceholder.h"
35 #include "core/accessibility/AXObjectCache.h"
36 #include "core/dom/Attribute.h"
37 #include "core/dom/ElementTraversal.h"
38 #include "core/dom/NodeTraversal.h"
39 #include "core/events/GestureEvent.h"
40 #include "core/events/KeyboardEvent.h"
41 #include "core/events/MouseEvent.h"
42 #include "core/frame/LocalFrame.h"
43 #include "core/html/FormDataList.h"
44 #include "core/html/HTMLFormElement.h"
45 #include "core/html/HTMLOptionElement.h"
46 #include "core/html/forms/FormController.h"
47 #include "core/page/EventHandler.h"
48 #include "core/page/SpatialNavigation.h"
49 #include "core/rendering/RenderListBox.h"
50 #include "core/rendering/RenderMenuList.h"
51 #include "core/rendering/RenderTheme.h"
52 #include "platform/PlatformMouseEvent.h"
53 #include "platform/text/PlatformLocale.h"
56 using namespace WTF::Unicode;
60 using namespace HTMLNames;
62 // Upper limit agreed upon with representatives of Opera and Mozilla.
63 static const unsigned maxSelectItems = 10000;
65 HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
66 : HTMLFormControlElementWithState(selectTag, document, form)
69 , m_lastOnChangeIndex(-1)
70 , m_activeSelectionAnchorIndex(-1)
71 , m_activeSelectionEndIndex(-1)
72 , m_isProcessingUserDrivenChange(false)
74 , m_activeSelectionState(false)
75 , m_shouldRecalcListItems(false)
76 , m_suggestedIndex(-1)
78 ScriptWrappable::init(this);
81 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
83 return adoptRef(new HTMLSelectElement(document, 0));
86 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
88 return adoptRef(new HTMLSelectElement(document, form));
91 const AtomicString& HTMLSelectElement::formControlType() const
93 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
94 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
95 return m_multiple ? selectMultiple : selectOne;
98 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
100 // User interaction such as mousedown events can cause list box select elements to send change events.
101 // This produces that same behavior for changes triggered by other code running on behalf of the user.
102 if (!usesMenuList()) {
103 updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
104 setNeedsValidityCheck();
110 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
111 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
112 // The selectOption function does not behave this way, possibly because other callers need a change event even
113 // in cases where the selected option is not change.
114 if (optionIndex == selectedIndex())
117 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchInputAndChangeEvent : 0) | UserDriven);
120 bool HTMLSelectElement::hasPlaceholderLabelOption() const
122 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
124 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
125 // Using "size() > 1" here because size() may be 0 in WebKit.
126 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
128 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
129 // In this case, the display size should be assumed as the default.
130 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
132 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
133 if (multiple() || size() > 1)
136 int listIndex = optionToListIndex(0);
137 ASSERT(listIndex >= 0);
140 return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
143 String HTMLSelectElement::validationMessage() const
148 return customValidationMessage();
150 return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
154 bool HTMLSelectElement::valueMissing() const
162 int firstSelectionIndex = selectedIndex();
164 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
165 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
168 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
171 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
173 updateSelectedState(listIndex, allowMultiplySelections, shift);
174 setNeedsValidityCheck();
180 bool HTMLSelectElement::usesMenuList() const
182 if (RenderTheme::theme().delegatesMenuListRendering())
185 return !m_multiple && m_size <= 1;
188 int HTMLSelectElement::activeSelectionStartListIndex() const
190 if (m_activeSelectionAnchorIndex >= 0)
191 return m_activeSelectionAnchorIndex;
192 return optionToListIndex(selectedIndex());
195 int HTMLSelectElement::activeSelectionEndListIndex() const
197 if (m_activeSelectionEndIndex >= 0)
198 return m_activeSelectionEndIndex;
199 return lastSelectedListIndex();
202 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
204 // Make sure the element is ref'd and deref'd so we don't leak it.
205 RefPtr<HTMLElement> protectNewChild(element);
207 if (!element || !(isHTMLOptionElement(element) || isHTMLOptGroupElement(element) || isHTMLHRElement(element)))
210 insertBefore(element, before, exceptionState);
211 setNeedsValidityCheck();
214 void HTMLSelectElement::addBeforeOptionAtIndex(HTMLElement* element, int beforeIndex, ExceptionState& exceptionState)
216 HTMLElement* beforeElement = toHTMLElement(options()->item(beforeIndex));
217 add(element, beforeElement, exceptionState);
220 void HTMLSelectElement::remove(int optionIndex)
222 int listIndex = optionToListIndex(optionIndex);
226 listItems()[listIndex]->remove(IGNORE_EXCEPTION);
229 String HTMLSelectElement::value() const
231 const Vector<HTMLElement*>& items = listItems();
232 for (unsigned i = 0; i < items.size(); i++) {
233 if (isHTMLOptionElement(items[i]) && toHTMLOptionElement(items[i])->selected())
234 return toHTMLOptionElement(items[i])->value();
239 void HTMLSelectElement::setValue(const String &value, bool sendEvents)
241 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
242 unsigned optionIndex = 0;
243 if (value.isNull()) {
246 // Find the option with value() matching the given parameter and make it the current selection.
247 const Vector<HTMLElement*>& items = listItems();
248 for (unsigned i = 0; i < items.size(); i++) {
249 if (isHTMLOptionElement(items[i])) {
250 if (toHTMLOptionElement(items[i])->value() == value)
255 if (optionIndex >= items.size())
258 setSelectedIndex(optionIndex);
262 dispatchInputAndChangeEventForMenuList(false);
268 String HTMLSelectElement::suggestedValue() const
270 const Vector<HTMLElement*>& items = listItems();
271 for (unsigned i = 0; i < items.size(); ++i) {
272 if (isHTMLOptionElement(items[i]) && m_suggestedIndex >= 0) {
273 if (i == static_cast<unsigned>(m_suggestedIndex))
274 return toHTMLOptionElement(items[i])->value();
280 void HTMLSelectElement::setSuggestedValue(const String& value)
282 if (value.isNull()) {
283 setSuggestedIndex(-1);
287 const Vector<HTMLElement*>& items = listItems();
288 unsigned optionIndex = 0;
289 for (unsigned i = 0; i < items.size(); ++i) {
290 if (isHTMLOptionElement(items[i])) {
291 if (toHTMLOptionElement(items[i])->value() == value) {
292 setSuggestedIndex(optionIndex);
299 setSuggestedIndex(-1);
302 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
304 if (name == alignAttr) {
305 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
306 // See http://bugs.webkit.org/show_bug.cgi?id=12072
310 return HTMLFormControlElementWithState::isPresentationAttribute(name);
313 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
315 if (name == sizeAttr) {
316 int oldSize = m_size;
317 // Set the attribute value to a number.
318 // This is important since the style rules for this attribute can determine the appearance property.
319 int size = value.toInt();
320 AtomicString attrSize = AtomicString::number(size);
321 if (attrSize != value) {
322 // FIXME: This is horribly factored.
323 if (Attribute* sizeAttribute = ensureUniqueElementData().getAttributeItem(sizeAttr))
324 sizeAttribute->setValue(attrSize);
328 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
330 updateListItemSelectedStates();
333 setNeedsValidityCheck();
334 if (m_size != oldSize && inActiveDocument()) {
335 lazyReattachIfAttached();
336 setRecalcListItems();
338 } else if (name == multipleAttr)
339 parseMultipleAttribute(value);
340 else if (name == accesskeyAttr) {
341 // FIXME: ignore for the moment.
343 } else if (name == disabledAttr) {
344 HTMLFormControlElementWithState::parseAttribute(name, value);
345 if (renderer() && renderer()->isMenuList()) {
346 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
347 if (menuList->popupIsVisible())
348 menuList->hidePopup();
353 HTMLFormControlElementWithState::parseAttribute(name, value);
356 bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
361 bool HTMLSelectElement::canSelectAll() const
363 return !usesMenuList();
366 RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
369 return new RenderMenuList(this);
370 return new RenderListBox(this);
373 PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
375 updateListItemSelectedStates();
376 return ensureCachedHTMLCollection(SelectedOptions);
379 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
381 return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get());
384 void HTMLSelectElement::updateListItemSelectedStates()
386 if (!m_shouldRecalcListItems)
389 setNeedsValidityCheck();
392 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
394 setRecalcListItems();
395 setNeedsValidityCheck();
396 m_lastOnChangeSelection.clear();
398 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
401 void HTMLSelectElement::optionElementChildrenChanged()
403 setRecalcListItems();
404 setNeedsValidityCheck();
407 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
408 cache->childrenChanged(this);
412 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
415 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
418 void HTMLSelectElement::setMultiple(bool multiple)
420 bool oldMultiple = this->multiple();
421 int oldSelectedIndex = selectedIndex();
422 setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
424 // Restore selectedIndex after changing the multiple flag to preserve
425 // selection as single-line and multi-line has different defaults.
426 if (oldMultiple != this->multiple())
427 setSelectedIndex(oldSelectedIndex);
430 void HTMLSelectElement::setSize(int size)
432 setIntegralAttribute(sizeAttr, size);
435 Element* HTMLSelectElement::namedItem(const AtomicString& name)
437 return options()->namedItem(name);
440 Element* HTMLSelectElement::item(unsigned index)
442 return options()->item(index);
445 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
447 if (index > maxSelectItems - 1)
448 index = maxSelectItems - 1;
449 int diff = index - length();
450 RefPtr<HTMLElement> before = nullptr;
451 // Out of array bounds? First insert empty dummies.
453 setLength(index, exceptionState);
454 // Replace an existing entry?
455 } else if (diff < 0) {
456 before = toHTMLElement(options()->item(index+1));
459 // Finally add the new element.
460 if (!exceptionState.hadException()) {
461 add(option, before.get(), exceptionState);
462 if (diff >= 0 && option->selected())
463 optionSelectionStateChanged(option, true);
467 void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
469 if (newLen > maxSelectItems)
470 newLen = maxSelectItems;
471 int diff = length() - newLen;
473 if (diff < 0) { // Add dummy elements.
475 RefPtr<Element> option = document().createElement(optionTag, false);
477 add(toHTMLElement(option), 0, exceptionState);
478 if (exceptionState.hadException())
482 const Vector<HTMLElement*>& items = listItems();
484 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
485 // of elements that we intend to remove then attempt to remove them one at a time.
486 Vector<RefPtr<Element> > itemsToRemove;
487 size_t optionIndex = 0;
488 for (size_t i = 0; i < items.size(); ++i) {
489 Element* item = items[i];
490 if (isHTMLOptionElement(items[i]) && optionIndex++ >= newLen) {
491 ASSERT(item->parentNode());
492 itemsToRemove.append(item);
496 for (size_t i = 0; i < itemsToRemove.size(); ++i) {
497 Element* item = itemsToRemove[i].get();
498 if (item->parentNode())
499 item->parentNode()->removeChild(item, exceptionState);
502 setNeedsValidityCheck();
505 bool HTMLSelectElement::isRequiredFormControl() const
510 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
511 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
512 // Otherwise, it returns |listIndex|.
513 // Valid means that it is enabled and an option element.
514 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
516 ASSERT(direction == -1 || direction == 1);
517 const Vector<HTMLElement*>& listItems = this->listItems();
518 int lastGoodIndex = listIndex;
519 int size = listItems.size();
520 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
522 if (!listItems[listIndex]->isDisabledFormControl() && isHTMLOptionElement(*listItems[listIndex])) {
523 lastGoodIndex = listIndex;
528 return lastGoodIndex;
531 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
533 return nextValidIndex(startIndex, SkipForwards, 1);
536 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
538 if (startIndex == -1)
539 startIndex = listItems().size();
540 return nextValidIndex(startIndex, SkipBackwards, 1);
543 int HTMLSelectElement::firstSelectableListIndex() const
545 const Vector<HTMLElement*>& items = listItems();
546 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
547 if (static_cast<size_t>(index) == items.size())
552 int HTMLSelectElement::lastSelectableListIndex() const
554 return nextValidIndex(-1, SkipForwards, INT_MAX);
557 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
558 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
560 const Vector<HTMLElement*>& items = listItems();
561 // Can't use m_size because renderer forces a minimum size.
563 if (renderer()->isListBox())
564 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
566 // One page away, but not outside valid bounds.
567 // If there is a valid option item one page away, the index is chosen.
568 // If there is no exact one page away valid option, returns startIndex or the most far index.
569 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
570 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
571 return nextValidIndex(edgeIndex, direction, skipAmount);
574 void HTMLSelectElement::selectAll()
576 ASSERT(!usesMenuList());
577 if (!renderer() || !m_multiple)
580 // Save the selection so it can be compared to the new selectAll selection
581 // when dispatching change events.
584 m_activeSelectionState = true;
585 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
586 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
588 updateListBoxSelection(false);
590 setNeedsValidityCheck();
593 void HTMLSelectElement::saveLastSelection()
595 if (usesMenuList()) {
596 m_lastOnChangeIndex = selectedIndex();
600 m_lastOnChangeSelection.clear();
601 const Vector<HTMLElement*>& items = listItems();
602 for (unsigned i = 0; i < items.size(); ++i) {
603 HTMLElement* element = items[i];
604 m_lastOnChangeSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
608 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
610 m_activeSelectionAnchorIndex = index;
612 // Cache the selection state so we can restore the old selection as the new
613 // selection pivots around this anchor index.
614 m_cachedStateForActiveSelection.clear();
616 const Vector<HTMLElement*>& items = listItems();
617 for (unsigned i = 0; i < items.size(); ++i) {
618 HTMLElement* element = items[i];
619 m_cachedStateForActiveSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
623 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
625 m_activeSelectionEndIndex = index;
628 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
630 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
631 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
633 unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
634 unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
636 const Vector<HTMLElement*>& items = listItems();
637 for (unsigned i = 0; i < items.size(); ++i) {
638 HTMLElement* element = items[i];
639 if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
642 if (i >= start && i <= end)
643 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
644 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
645 toHTMLOptionElement(element)->setSelectedState(false);
647 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
651 setNeedsValidityCheck();
652 notifyFormStateChanged();
655 void HTMLSelectElement::listBoxOnChange()
657 ASSERT(!usesMenuList() || m_multiple);
659 const Vector<HTMLElement*>& items = listItems();
661 // If the cached selection list is empty, or the size has changed, then fire
662 // dispatchFormControlChangeEvent, and return early.
663 // FIXME: Why? This looks unreasonable.
664 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
665 dispatchFormControlChangeEvent();
669 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
670 bool fireOnChange = false;
671 for (unsigned i = 0; i < items.size(); ++i) {
672 HTMLElement* element = items[i];
673 bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
674 if (selected != m_lastOnChangeSelection[i])
676 m_lastOnChangeSelection[i] = selected;
680 RefPtr<HTMLSelectElement> protector(this);
681 dispatchInputEvent();
682 dispatchFormControlChangeEvent();
686 void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUserGesture)
688 ASSERT(usesMenuList());
690 int selected = selectedIndex();
691 if (m_lastOnChangeIndex != selected && (!requiresUserGesture || m_isProcessingUserDrivenChange)) {
692 m_lastOnChangeIndex = selected;
693 m_isProcessingUserDrivenChange = false;
694 RefPtr<HTMLSelectElement> protector(this);
695 dispatchInputEvent();
696 dispatchFormControlChangeEvent();
700 void HTMLSelectElement::scrollToSelection()
705 if (RenderObject* renderer = this->renderer())
706 toRenderListBox(renderer)->selectionChanged();
709 void HTMLSelectElement::setOptionsChangedOnRenderer()
711 if (RenderObject* renderer = this->renderer()) {
713 toRenderMenuList(renderer)->setOptionsChanged(true);
715 toRenderListBox(renderer)->setOptionsChanged(true);
719 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
721 if (m_shouldRecalcListItems)
725 Vector<HTMLElement*> items = m_listItems;
726 recalcListItems(false);
727 ASSERT(items == m_listItems);
734 void HTMLSelectElement::invalidateSelectedItems()
736 if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
737 collection->invalidateCache();
740 void HTMLSelectElement::setRecalcListItems()
742 // FIXME: This function does a bunch of confusing things depending on if it
743 // is in the document or not.
745 m_shouldRecalcListItems = true;
746 // Manual selection anchor is reset when manipulating the select programmatically.
747 m_activeSelectionAnchorIndex = -1;
748 setOptionsChangedOnRenderer();
749 setNeedsStyleRecalc(SubtreeStyleChange);
751 if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
752 collection->invalidateCache();
755 invalidateSelectedItems();
758 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
759 cache->childrenChanged(this);
763 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
767 m_shouldRecalcListItems = false;
769 HTMLOptionElement* foundSelected = 0;
770 HTMLOptionElement* firstOption = 0;
771 for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
772 if (!currentElement->isHTMLElement()) {
773 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
776 HTMLElement& current = toHTMLElement(*currentElement);
778 // optgroup tags may not nest. However, both FireFox and IE will
779 // flatten the tree automatically, so we follow suit.
780 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
781 if (isHTMLOptGroupElement(current)) {
782 m_listItems.append(¤t);
783 if (Element* nextElement = ElementTraversal::firstWithin(current)) {
784 currentElement = nextElement;
789 if (isHTMLOptionElement(current)) {
790 m_listItems.append(¤t);
792 if (updateSelectedStates && !m_multiple) {
793 HTMLOptionElement& option = toHTMLOptionElement(current);
795 firstOption = &option;
796 if (option.selected()) {
798 foundSelected->setSelectedState(false);
799 foundSelected = &option;
800 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
801 foundSelected = &option;
802 foundSelected->setSelectedState(true);
807 if (isHTMLHRElement(current))
808 m_listItems.append(¤t);
810 // In conforming HTML code, only <optgroup> and <option> will be found
811 // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
812 // into those tags that we choose to. For web-compat, we should cope
813 // with the case where odd tags like a <div> have been added but we
814 // handle this because such tags have already been removed from the
815 // <select>'s subtree at this point.
816 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
819 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
820 firstOption->setSelectedState(true);
823 int HTMLSelectElement::selectedIndex() const
827 // Return the number of the first option selected.
828 const Vector<HTMLElement*>& items = listItems();
829 for (size_t i = 0; i < items.size(); ++i) {
830 HTMLElement* element = items[i];
831 if (isHTMLOptionElement(*element)) {
832 if (toHTMLOptionElement(*element).selected())
841 void HTMLSelectElement::setSelectedIndex(int index)
843 selectOption(index, DeselectOtherOptions);
846 int HTMLSelectElement::suggestedIndex() const
848 return m_suggestedIndex;
851 void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
853 m_suggestedIndex = suggestedIndex;
855 if (RenderObject* renderer = this->renderer()) {
856 renderer->updateFromElement();
857 if (renderer->isListBox())
858 toRenderListBox(renderer)->scrollToRevealElementAtListIndex(suggestedIndex);
862 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
864 ASSERT(option->ownerSelectElement() == this);
865 if (optionIsSelected)
866 selectOption(option->index());
867 else if (!usesMenuList() || multiple())
870 selectOption(nextSelectableListIndex(-1));
873 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
875 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
877 const Vector<HTMLElement*>& items = listItems();
878 int listIndex = optionToListIndex(optionIndex);
880 HTMLElement* element = 0;
881 if (listIndex >= 0) {
882 element = items[listIndex];
883 if (isHTMLOptionElement(*element)) {
884 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
885 setActiveSelectionAnchorIndex(listIndex);
886 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
887 setActiveSelectionEndIndex(listIndex);
888 toHTMLOptionElement(*element).setSelectedState(true);
893 deselectItemsWithoutValidation(element);
895 // For the menu list case, this is what makes the selected element appear.
896 if (RenderObject* renderer = this->renderer())
897 renderer->updateFromElement();
901 if (usesMenuList()) {
902 m_isProcessingUserDrivenChange = flags & UserDriven;
903 if (flags & DispatchInputAndChangeEvent)
904 dispatchInputAndChangeEventForMenuList();
905 if (RenderObject* renderer = this->renderer()) {
907 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
908 else if (renderer->isListBox())
909 toRenderListBox(renderer)->selectionChanged();
913 setNeedsValidityCheck();
914 notifyFormStateChanged();
917 int HTMLSelectElement::optionToListIndex(int optionIndex) const
919 const Vector<HTMLElement*>& items = listItems();
920 int listSize = static_cast<int>(items.size());
921 if (optionIndex < 0 || optionIndex >= listSize)
924 int optionIndex2 = -1;
925 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
926 if (isHTMLOptionElement(*items[listIndex])) {
928 if (optionIndex2 == optionIndex)
936 int HTMLSelectElement::listToOptionIndex(int listIndex) const
938 const Vector<HTMLElement*>& items = listItems();
939 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(*items[listIndex]))
942 // Actual index of option not counting OPTGROUP entries that may be in list.
944 for (int i = 0; i < listIndex; ++i) {
945 if (isHTMLOptionElement(*items[i]))
952 void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
954 // Save the selection so it can be compared to the new selection when
955 // dispatching change events during blur event dispatch.
958 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, type);
961 void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
963 // We only need to fire change events here for menu lists, because we fire
964 // change events for list boxes whenever the selection change is actually made.
965 // This matches other browsers' behavior.
967 dispatchInputAndChangeEventForMenuList();
968 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
971 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
973 const Vector<HTMLElement*>& items = listItems();
974 for (unsigned i = 0; i < items.size(); ++i) {
975 HTMLElement* element = items[i];
976 if (element != excludeElement && isHTMLOptionElement(*element))
977 toHTMLOptionElement(element)->setSelectedState(false);
981 FormControlState HTMLSelectElement::saveFormControlState() const
983 const Vector<HTMLElement*>& items = listItems();
984 size_t length = items.size();
985 FormControlState state;
986 for (unsigned i = 0; i < length; ++i) {
987 if (!isHTMLOptionElement(*items[i]))
989 HTMLOptionElement* option = toHTMLOptionElement(items[i]);
990 if (!option->selected())
992 state.append(option->value());
999 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
1001 const Vector<HTMLElement*>& items = listItems();
1002 size_t loopEndIndex = std::min(items.size(), listIndexEnd);
1003 for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
1004 if (!isHTMLOptionElement(items[i]))
1006 if (toHTMLOptionElement(items[i])->value() == value)
1012 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
1016 const Vector<HTMLElement*>& items = listItems();
1017 size_t itemsSize = items.size();
1021 for (size_t i = 0; i < itemsSize; ++i) {
1022 if (!isHTMLOptionElement(items[i]))
1024 toHTMLOptionElement(items[i])->setSelectedState(false);
1028 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
1029 if (foundIndex != kNotFound)
1030 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
1032 size_t startIndex = 0;
1033 for (size_t i = 0; i < state.valueSize(); ++i) {
1034 const String& value = state[i];
1035 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
1036 if (foundIndex == kNotFound)
1037 foundIndex = searchOptionsForValue(value, 0, startIndex);
1038 if (foundIndex == kNotFound)
1040 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
1041 startIndex = foundIndex + 1;
1045 setOptionsChangedOnRenderer();
1046 setNeedsValidityCheck();
1049 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
1051 bool oldUsesMenuList = usesMenuList();
1052 m_multiple = !value.isNull();
1053 setNeedsValidityCheck();
1054 if (oldUsesMenuList != usesMenuList())
1055 lazyReattachIfAttached();
1058 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
1060 const AtomicString& name = this->name();
1064 bool successful = false;
1065 const Vector<HTMLElement*>& items = listItems();
1067 for (unsigned i = 0; i < items.size(); ++i) {
1068 HTMLElement* element = items[i];
1069 if (isHTMLOptionElement(*element) && toHTMLOptionElement(*element).selected() && !toHTMLOptionElement(*element).isDisabledFormControl()) {
1070 list.appendData(name, toHTMLOptionElement(*element).value());
1075 // It's possible that this is a menulist with multiple options and nothing
1076 // will be submitted (!successful). We won't send a unselected non-disabled
1077 // option as fallback. This behavior matches to other browsers.
1081 void HTMLSelectElement::resetImpl()
1083 HTMLOptionElement* firstOption = 0;
1084 HTMLOptionElement* selectedOption = 0;
1086 const Vector<HTMLElement*>& items = listItems();
1087 for (unsigned i = 0; i < items.size(); ++i) {
1088 HTMLElement* element = items[i];
1089 if (!isHTMLOptionElement(*element))
1092 if (items[i]->fastHasAttribute(selectedAttr)) {
1093 if (selectedOption && !m_multiple)
1094 selectedOption->setSelectedState(false);
1095 toHTMLOptionElement(element)->setSelectedState(true);
1096 selectedOption = toHTMLOptionElement(element);
1098 toHTMLOptionElement(element)->setSelectedState(false);
1101 firstOption = toHTMLOptionElement(element);
1104 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
1105 firstOption->setSelectedState(true);
1107 setOptionsChangedOnRenderer();
1108 setNeedsStyleRecalc(SubtreeStyleChange);
1109 setNeedsValidityCheck();
1113 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
1115 if (!RenderTheme::theme().popsMenuByArrowKeys())
1118 if (!isSpatialNavigationEnabled(document().frame())) {
1119 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
1121 // Calling focus() may cause us to lose our renderer. Return true so
1122 // that our caller doesn't process the event further, but don't set
1123 // the event as handled.
1124 if (!renderer() || isDisabledFormControl())
1127 // Save the selection so it can be compared to the new selection
1128 // when dispatching change events during selectOption, which
1129 // gets called from RenderMenuList::valueChanged, which gets called
1130 // after the user makes a selection from the menu.
1131 saveLastSelection();
1132 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1133 menuList->showPopup();
1134 event->setDefaultHandled();
1143 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1145 RenderTheme& renderTheme = RenderTheme::theme();
1147 if (event->type() == EventTypeNames::keydown) {
1148 if (!renderer() || !event->isKeyboardEvent())
1151 if (platformHandleKeydownEvent(toKeyboardEvent(event)))
1154 // When using spatial navigation, we want to be able to navigate away
1155 // from the select element when the user hits any of the arrow keys,
1156 // instead of changing the selection.
1157 if (isSpatialNavigationEnabled(document().frame())) {
1158 if (!m_activeSelectionState)
1162 const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
1163 bool handled = true;
1164 const Vector<HTMLElement*>& listItems = this->listItems();
1165 int listIndex = optionToListIndex(selectedIndex());
1167 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1168 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1169 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1170 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1171 else if (keyIdentifier == "PageDown")
1172 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1173 else if (keyIdentifier == "PageUp")
1174 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1175 else if (keyIdentifier == "Home")
1176 listIndex = nextValidIndex(-1, SkipForwards, 1);
1177 else if (keyIdentifier == "End")
1178 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1182 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1183 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
1186 event->setDefaultHandled();
1189 // Use key press event here since sending simulated mouse events
1190 // on key down blocks the proper sending of the key press event.
1191 if (event->type() == EventTypeNames::keypress) {
1192 if (!renderer() || !event->isKeyboardEvent())
1195 int keyCode = toKeyboardEvent(event)->keyCode();
1196 bool handled = false;
1198 if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1199 // Use space to toggle arrow key handling for selection change or spatial navigation.
1200 m_activeSelectionState = !m_activeSelectionState;
1201 event->setDefaultHandled();
1205 if (renderTheme.popsMenuBySpaceOrReturn()) {
1206 if (keyCode == ' ' || keyCode == '\r') {
1209 // Calling focus() may remove the renderer or change the
1211 if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
1214 // Save the selection so it can be compared to the new selection
1215 // when dispatching change events during selectOption, which
1216 // gets called from RenderMenuList::valueChanged, which gets called
1217 // after the user makes a selection from the menu.
1218 saveLastSelection();
1219 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1220 menuList->showPopup();
1223 } else if (renderTheme.popsMenuByArrowKeys()) {
1224 if (keyCode == ' ') {
1227 // Calling focus() may remove the renderer or change the
1229 if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
1232 // Save the selection so it can be compared to the new selection
1233 // when dispatching change events during selectOption, which
1234 // gets called from RenderMenuList::valueChanged, which gets called
1235 // after the user makes a selection from the menu.
1236 saveLastSelection();
1237 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1238 menuList->showPopup();
1240 } else if (keyCode == '\r') {
1242 form()->submitImplicitly(event, false);
1243 dispatchInputAndChangeEventForMenuList();
1249 event->setDefaultHandled();
1252 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1254 if (renderer() && renderer()->isMenuList() && !isDisabledFormControl()) {
1255 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1256 if (menuList->popupIsVisible())
1257 menuList->hidePopup();
1259 // Save the selection so it can be compared to the new
1260 // selection when we call onChange during selectOption,
1261 // which gets called from RenderMenuList::valueChanged,
1262 // which gets called after the user makes a selection from
1264 saveLastSelection();
1265 menuList->showPopup();
1269 event->setDefaultHandled();
1272 if (event->type() == EventTypeNames::blur) {
1273 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1274 if (menuList->popupIsVisible())
1275 menuList->hidePopup();
1280 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1282 ASSERT(listIndex >= 0);
1284 // Save the selection so it can be compared to the new selection when
1285 // dispatching change events during mouseup, or after autoscroll finishes.
1286 saveLastSelection();
1288 m_activeSelectionState = true;
1290 bool shiftSelect = m_multiple && shift;
1291 bool multiSelect = m_multiple && multi && !shift;
1293 HTMLElement* clickedElement = listItems()[listIndex];
1294 ASSERT(clickedElement);
1295 if (isHTMLOptionElement(*clickedElement)) {
1296 // Keep track of whether an active selection (like during drag
1297 // selection), should select or deselect.
1298 if (toHTMLOptionElement(*clickedElement).selected() && multiSelect)
1299 m_activeSelectionState = false;
1300 if (!m_activeSelectionState)
1301 toHTMLOptionElement(*clickedElement).setSelectedState(false);
1304 // If we're not in any special multiple selection mode, then deselect all
1305 // other items, excluding the clicked option. If no option was clicked, then
1306 // this will deselect all items in the list.
1307 if (!shiftSelect && !multiSelect)
1308 deselectItemsWithoutValidation(clickedElement);
1310 // If the anchor hasn't been set, and we're doing a single selection or a
1311 // shift selection, then initialize the anchor to the first selected index.
1312 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1313 setActiveSelectionAnchorIndex(selectedIndex());
1315 // Set the selection state of the clicked option.
1316 if (isHTMLOptionElement(*clickedElement) && !toHTMLOptionElement(*clickedElement).isDisabledFormControl())
1317 toHTMLOptionElement(*clickedElement).setSelectedState(true);
1319 // If there was no selectedIndex() for the previous initialization, or If
1320 // we're doing a single selection, or a multiple selection (using cmd or
1321 // ctrl), then initialize the anchor index to the listIndex that just got
1323 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1324 setActiveSelectionAnchorIndex(listIndex);
1326 setActiveSelectionEndIndex(listIndex);
1327 updateListBoxSelection(!multiSelect);
1330 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1332 const Vector<HTMLElement*>& listItems = this->listItems();
1333 bool dragSelection = false;
1334 if (event->type() == EventTypeNames::gesturetap && event->isGestureEvent()) {
1336 // Calling focus() may cause us to lose our renderer or change the render type, in which case do not want to handle the event.
1337 if (!renderer() || !renderer()->isListBox())
1340 // Convert to coords relative to the list box if needed.
1341 GestureEvent& gestureEvent = toGestureEvent(*event);
1342 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(gestureEvent.absoluteLocation(), UseTransforms));
1343 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
1344 if (listIndex >= 0) {
1345 if (!isDisabledFormControl())
1346 updateSelectedState(listIndex, true, gestureEvent.shiftKey());
1347 event->setDefaultHandled();
1349 } else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1351 // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
1355 // Convert to coords relative to the list box if needed.
1356 MouseEvent* mouseEvent = toMouseEvent(event);
1357 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
1358 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
1359 if (listIndex >= 0) {
1360 if (!isDisabledFormControl()) {
1362 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
1364 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
1367 if (LocalFrame* frame = document().frame())
1368 frame->eventHandler().setMouseDownMayStartAutoscroll();
1370 event->setDefaultHandled();
1372 } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
1373 MouseEvent* mouseEvent = toMouseEvent(event);
1374 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
1377 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
1378 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
1379 if (listIndex >= 0) {
1380 if (!isDisabledFormControl()) {
1382 // Only extend selection if there is something selected.
1383 if (m_activeSelectionAnchorIndex < 0)
1386 setActiveSelectionEndIndex(listIndex);
1387 updateListBoxSelection(false);
1389 setActiveSelectionAnchorIndex(listIndex);
1390 setActiveSelectionEndIndex(listIndex);
1391 updateListBoxSelection(true);
1394 dragSelection = true;
1396 } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
1397 // We didn't start this click/drag on any options.
1398 if (m_lastOnChangeSelection.isEmpty())
1400 // This makes sure we fire dispatchFormControlChangeEvent for a single
1401 // click. For drag selection, onChange will fire when the autoscroll
1403 if (!dragSelection) {
1406 } else if (event->type() == EventTypeNames::keydown) {
1407 if (!event->isKeyboardEvent())
1409 const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
1411 bool handled = false;
1413 if (m_activeSelectionEndIndex < 0) {
1414 // Initialize the end index
1415 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1416 int startIndex = lastSelectedListIndex();
1418 if (keyIdentifier == "Down")
1419 endIndex = nextSelectableListIndex(startIndex);
1421 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1422 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1423 int startIndex = optionToListIndex(selectedIndex());
1425 if (keyIdentifier == "Up")
1426 endIndex = previousSelectableListIndex(startIndex);
1428 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1431 // Set the end index based on the current end index.
1432 if (keyIdentifier == "Down") {
1433 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1435 } else if (keyIdentifier == "Up") {
1436 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1438 } else if (keyIdentifier == "PageDown") {
1439 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1441 } else if (keyIdentifier == "PageUp") {
1442 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1446 if (keyIdentifier == "Home") {
1447 endIndex = firstSelectableListIndex();
1449 } else if (keyIdentifier == "End") {
1450 endIndex = lastSelectableListIndex();
1454 if (isSpatialNavigationEnabled(document().frame()))
1455 // Check if the selection moves to the boundary.
1456 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1459 if (endIndex >= 0 && handled) {
1460 // Save the selection so it can be compared to the new selection
1461 // when dispatching change events immediately after making the new
1463 saveLastSelection();
1465 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1466 setActiveSelectionEndIndex(endIndex);
1468 bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
1470 m_activeSelectionState = true;
1471 // If the anchor is unitialized, or if we're going to deselect all
1472 // other options, then set the anchor index equal to the end index.
1473 bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
1474 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1476 deselectItemsWithoutValidation();
1477 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1480 toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
1481 if (selectNewItem) {
1482 updateListBoxSelection(deselectOthers);
1485 scrollToSelection();
1487 event->setDefaultHandled();
1489 } else if (event->type() == EventTypeNames::keypress) {
1490 if (!event->isKeyboardEvent())
1492 int keyCode = toKeyboardEvent(event)->keyCode();
1494 if (keyCode == '\r') {
1496 form()->submitImplicitly(event, false);
1497 event->setDefaultHandled();
1498 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1499 // Use space to toggle selection change.
1500 m_activeSelectionState = !m_activeSelectionState;
1501 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
1503 event->setDefaultHandled();
1508 void HTMLSelectElement::defaultEventHandler(Event* event)
1513 if (isDisabledFormControl()) {
1514 HTMLFormControlElementWithState::defaultEventHandler(event);
1519 menuListDefaultEventHandler(event);
1521 listBoxDefaultEventHandler(event);
1522 if (event->defaultHandled())
1525 if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
1526 KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
1527 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
1528 typeAheadFind(keyboardEvent);
1529 event->setDefaultHandled();
1533 HTMLFormControlElementWithState::defaultEventHandler(event);
1536 int HTMLSelectElement::lastSelectedListIndex() const
1538 const Vector<HTMLElement*>& items = listItems();
1539 for (size_t i = items.size(); i;) {
1540 HTMLElement* element = items[--i];
1541 if (isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected())
1547 int HTMLSelectElement::indexOfSelectedOption() const
1549 return optionToListIndex(selectedIndex());
1552 int HTMLSelectElement::optionCount() const
1554 return listItems().size();
1557 String HTMLSelectElement::optionAtIndex(int index) const
1559 const Vector<HTMLElement*>& items = listItems();
1561 HTMLElement* element = items[index];
1562 if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
1564 return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
1567 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
1569 int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
1572 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
1573 if (!usesMenuList())
1577 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
1579 // When the element is created during document parsing, it won't have any
1580 // items yet - but for innerHTML and related methods, this method is called
1581 // after the whole subtree is constructed.
1583 HTMLFormControlElementWithState::insertedInto(insertionPoint);
1584 return InsertionDone;
1587 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1589 // First bring into focus the list box.
1591 accessKeyAction(false);
1593 // If this index is already selected, unselect. otherwise update the selected index.
1594 const Vector<HTMLElement*>& items = listItems();
1595 int listIndex = optionToListIndex(index);
1596 if (listIndex >= 0) {
1597 HTMLElement* element = items[listIndex];
1598 if (isHTMLOptionElement(*element)) {
1599 if (toHTMLOptionElement(*element).selected())
1600 toHTMLOptionElement(*element).setSelectedState(false);
1602 selectOption(index, DispatchInputAndChangeEvent | UserDriven);
1607 dispatchInputAndChangeEventForMenuList();
1611 scrollToSelection();
1614 unsigned HTMLSelectElement::length() const
1616 unsigned options = 0;
1618 const Vector<HTMLElement*>& items = listItems();
1619 for (unsigned i = 0; i < items.size(); ++i) {
1620 if (isHTMLOptionElement(*items[i]))
1627 void HTMLSelectElement::finishParsingChildren()
1629 HTMLFormControlElementWithState::finishParsingChildren();
1630 updateListItemSelectedStates();
1633 bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
1635 if (!value) { // undefined or null
1639 setOption(index, value.get(), exceptionState);
1643 bool HTMLSelectElement::isInteractiveContent() const
1648 bool HTMLSelectElement::supportsAutofocus() const