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/KeyboardEvent.h"
40 #include "core/events/MouseEvent.h"
41 #include "core/events/ThreadLocalEventNames.h"
42 #include "core/html/FormDataList.h"
43 #include "core/html/HTMLFormElement.h"
44 #include "core/html/HTMLOptionElement.h"
45 #include "core/html/forms/FormController.h"
46 #include "core/page/EventHandler.h"
47 #include "core/frame/Frame.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)
77 ScriptWrappable::init(this);
80 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
82 return adoptRef(new HTMLSelectElement(document, 0));
85 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
87 return adoptRef(new HTMLSelectElement(document, form));
90 const AtomicString& HTMLSelectElement::formControlType() const
92 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
93 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
94 return m_multiple ? selectMultiple : selectOne;
97 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
99 // User interaction such as mousedown events can cause list box select elements to send change events.
100 // This produces that same behavior for changes triggered by other code running on behalf of the user.
101 if (!usesMenuList()) {
102 updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
103 setNeedsValidityCheck();
109 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
110 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
111 // The selectOption function does not behave this way, possibly because other callers need a change event even
112 // in cases where the selected option is not change.
113 if (optionIndex == selectedIndex())
116 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
119 bool HTMLSelectElement::hasPlaceholderLabelOption() const
121 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
123 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
124 // Using "size() > 1" here because size() may be 0 in WebKit.
125 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
127 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
128 // In this case, the display size should be assumed as the default.
129 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
131 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
132 if (multiple() || size() > 1)
135 int listIndex = optionToListIndex(0);
136 ASSERT(listIndex >= 0);
139 return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
142 String HTMLSelectElement::validationMessage() const
147 return customValidationMessage();
149 return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
153 bool HTMLSelectElement::valueMissing() const
161 int firstSelectionIndex = selectedIndex();
163 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
164 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
167 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
170 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
172 updateSelectedState(listIndex, allowMultiplySelections, shift);
173 setNeedsValidityCheck();
179 bool HTMLSelectElement::usesMenuList() const
181 if (RenderTheme::theme().delegatesMenuListRendering())
184 return !m_multiple && m_size <= 1;
187 int HTMLSelectElement::activeSelectionStartListIndex() const
189 if (m_activeSelectionAnchorIndex >= 0)
190 return m_activeSelectionAnchorIndex;
191 return optionToListIndex(selectedIndex());
194 int HTMLSelectElement::activeSelectionEndListIndex() const
196 if (m_activeSelectionEndIndex >= 0)
197 return m_activeSelectionEndIndex;
198 return lastSelectedListIndex();
201 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
203 // Make sure the element is ref'd and deref'd so we don't leak it.
204 RefPtr<HTMLElement> protectNewChild(element);
206 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
209 insertBefore(element, before, exceptionState);
210 setNeedsValidityCheck();
213 void HTMLSelectElement::remove(int optionIndex)
215 int listIndex = optionToListIndex(optionIndex);
219 listItems()[listIndex]->remove(IGNORE_EXCEPTION);
222 void HTMLSelectElement::remove(HTMLOptionElement* option)
224 if (option->ownerSelectElement() != this)
227 option->remove(IGNORE_EXCEPTION);
230 String HTMLSelectElement::value() const
232 const Vector<HTMLElement*>& items = listItems();
233 for (unsigned i = 0; i < items.size(); i++) {
234 if (items[i]->hasLocalName(optionTag) && toHTMLOptionElement(items[i])->selected())
235 return toHTMLOptionElement(items[i])->value();
240 void HTMLSelectElement::setValue(const String &value)
242 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
243 if (value.isNull()) {
244 setSelectedIndex(-1);
248 // Find the option with value() matching the given parameter and make it the current selection.
249 const Vector<HTMLElement*>& items = listItems();
250 unsigned optionIndex = 0;
251 for (unsigned i = 0; i < items.size(); i++) {
252 if (items[i]->hasLocalName(optionTag)) {
253 if (toHTMLOptionElement(items[i])->value() == value) {
254 setSelectedIndex(optionIndex);
261 setSelectedIndex(-1);
264 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
266 if (name == alignAttr) {
267 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
268 // See http://bugs.webkit.org/show_bug.cgi?id=12072
272 return HTMLFormControlElementWithState::isPresentationAttribute(name);
275 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
277 if (name == sizeAttr) {
278 int oldSize = m_size;
279 // Set the attribute value to a number.
280 // This is important since the style rules for this attribute can determine the appearance property.
281 int size = value.toInt();
282 AtomicString attrSize = AtomicString::number(size);
283 if (attrSize != value) {
284 // FIXME: This is horribly factored.
285 if (Attribute* sizeAttribute = ensureUniqueElementData()->getAttributeItem(sizeAttr))
286 sizeAttribute->setValue(attrSize);
290 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
292 updateListItemSelectedStates();
295 setNeedsValidityCheck();
296 if (m_size != oldSize && inActiveDocument()) {
297 lazyReattachIfAttached();
298 setRecalcListItems();
300 } else if (name == multipleAttr)
301 parseMultipleAttribute(value);
302 else if (name == accesskeyAttr) {
303 // FIXME: ignore for the moment.
306 HTMLFormControlElementWithState::parseAttribute(name, value);
309 bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
314 bool HTMLSelectElement::canSelectAll() const
316 return !usesMenuList();
319 RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
322 return new RenderMenuList(this);
323 return new RenderListBox(this);
326 PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
328 updateListItemSelectedStates();
329 return ensureCachedHTMLCollection(SelectedOptions);
332 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
334 return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get());
337 void HTMLSelectElement::updateListItemSelectedStates()
339 if (!m_shouldRecalcListItems)
342 setNeedsValidityCheck();
345 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
347 setRecalcListItems();
348 setNeedsValidityCheck();
349 m_lastOnChangeSelection.clear();
351 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
354 void HTMLSelectElement::optionElementChildrenChanged()
356 setRecalcListItems();
357 setNeedsValidityCheck();
360 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
361 cache->childrenChanged(this);
365 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
368 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
371 void HTMLSelectElement::setMultiple(bool multiple)
373 bool oldMultiple = this->multiple();
374 int oldSelectedIndex = selectedIndex();
375 setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
377 // Restore selectedIndex after changing the multiple flag to preserve
378 // selection as single-line and multi-line has different defaults.
379 if (oldMultiple != this->multiple())
380 setSelectedIndex(oldSelectedIndex);
383 void HTMLSelectElement::setSize(int size)
385 setIntegralAttribute(sizeAttr, size);
388 Element* HTMLSelectElement::namedItem(const AtomicString& name)
390 return options()->namedItem(name);
393 Element* HTMLSelectElement::item(unsigned index)
395 return options()->item(index);
398 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
400 if (index > maxSelectItems - 1)
401 index = maxSelectItems - 1;
402 int diff = index - length();
403 RefPtr<HTMLElement> before = 0;
404 // Out of array bounds? First insert empty dummies.
406 setLength(index, exceptionState);
407 // Replace an existing entry?
408 } else if (diff < 0) {
409 before = toHTMLElement(options()->item(index+1));
412 // Finally add the new element.
413 if (!exceptionState.hadException()) {
414 add(option, before.get(), exceptionState);
415 if (diff >= 0 && option->selected())
416 optionSelectionStateChanged(option, true);
420 void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
422 if (newLen > maxSelectItems)
423 newLen = maxSelectItems;
424 int diff = length() - newLen;
426 if (diff < 0) { // Add dummy elements.
428 RefPtr<Element> option = document().createElement(optionTag, false);
430 add(toHTMLElement(option), 0, exceptionState);
431 if (exceptionState.hadException())
435 const Vector<HTMLElement*>& items = listItems();
437 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
438 // of elements that we intend to remove then attempt to remove them one at a time.
439 Vector<RefPtr<Element> > itemsToRemove;
440 size_t optionIndex = 0;
441 for (size_t i = 0; i < items.size(); ++i) {
442 Element* item = items[i];
443 if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
444 ASSERT(item->parentNode());
445 itemsToRemove.append(item);
449 for (size_t i = 0; i < itemsToRemove.size(); ++i) {
450 Element* item = itemsToRemove[i].get();
451 if (item->parentNode())
452 item->parentNode()->removeChild(item, exceptionState);
455 setNeedsValidityCheck();
458 bool HTMLSelectElement::isRequiredFormControl() const
463 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
464 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
465 // Otherwise, it returns |listIndex|.
466 // Valid means that it is enabled and an option element.
467 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
469 ASSERT(direction == -1 || direction == 1);
470 const Vector<HTMLElement*>& listItems = this->listItems();
471 int lastGoodIndex = listIndex;
472 int size = listItems.size();
473 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
475 if (!listItems[listIndex]->isDisabledFormControl() && listItems[listIndex]->hasTagName(optionTag)) {
476 lastGoodIndex = listIndex;
481 return lastGoodIndex;
484 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
486 return nextValidIndex(startIndex, SkipForwards, 1);
489 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
491 if (startIndex == -1)
492 startIndex = listItems().size();
493 return nextValidIndex(startIndex, SkipBackwards, 1);
496 int HTMLSelectElement::firstSelectableListIndex() const
498 const Vector<HTMLElement*>& items = listItems();
499 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
500 if (static_cast<size_t>(index) == items.size())
505 int HTMLSelectElement::lastSelectableListIndex() const
507 return nextValidIndex(-1, SkipForwards, INT_MAX);
510 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
511 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
513 const Vector<HTMLElement*>& items = listItems();
514 // Can't use m_size because renderer forces a minimum size.
516 if (renderer()->isListBox())
517 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
519 // One page away, but not outside valid bounds.
520 // If there is a valid option item one page away, the index is chosen.
521 // If there is no exact one page away valid option, returns startIndex or the most far index.
522 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
523 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
524 return nextValidIndex(edgeIndex, direction, skipAmount);
527 void HTMLSelectElement::selectAll()
529 ASSERT(!usesMenuList());
530 if (!renderer() || !m_multiple)
533 // Save the selection so it can be compared to the new selectAll selection
534 // when dispatching change events.
537 m_activeSelectionState = true;
538 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
539 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
541 updateListBoxSelection(false);
543 setNeedsValidityCheck();
546 void HTMLSelectElement::saveLastSelection()
548 if (usesMenuList()) {
549 m_lastOnChangeIndex = selectedIndex();
553 m_lastOnChangeSelection.clear();
554 const Vector<HTMLElement*>& items = listItems();
555 for (unsigned i = 0; i < items.size(); ++i) {
556 HTMLElement* element = items[i];
557 m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
561 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
563 m_activeSelectionAnchorIndex = index;
565 // Cache the selection state so we can restore the old selection as the new
566 // selection pivots around this anchor index.
567 m_cachedStateForActiveSelection.clear();
569 const Vector<HTMLElement*>& items = listItems();
570 for (unsigned i = 0; i < items.size(); ++i) {
571 HTMLElement* element = items[i];
572 m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
576 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
578 m_activeSelectionEndIndex = index;
581 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
583 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
584 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
586 unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
587 unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
589 const Vector<HTMLElement*>& items = listItems();
590 for (unsigned i = 0; i < items.size(); ++i) {
591 HTMLElement* element = items[i];
592 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
595 if (i >= start && i <= end)
596 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
597 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
598 toHTMLOptionElement(element)->setSelectedState(false);
600 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
604 setNeedsValidityCheck();
605 notifyFormStateChanged();
608 void HTMLSelectElement::listBoxOnChange()
610 ASSERT(!usesMenuList() || m_multiple);
612 const Vector<HTMLElement*>& items = listItems();
614 // If the cached selection list is empty, or the size has changed, then fire
615 // dispatchFormControlChangeEvent, and return early.
616 // FIXME: Why? This looks unreasonable.
617 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
618 dispatchFormControlChangeEvent();
622 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
623 bool fireOnChange = false;
624 for (unsigned i = 0; i < items.size(); ++i) {
625 HTMLElement* element = items[i];
626 bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
627 if (selected != m_lastOnChangeSelection[i])
629 m_lastOnChangeSelection[i] = selected;
633 dispatchFormControlChangeEvent();
636 void HTMLSelectElement::dispatchChangeEventForMenuList()
638 ASSERT(usesMenuList());
640 int selected = selectedIndex();
641 if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
642 m_lastOnChangeIndex = selected;
643 m_isProcessingUserDrivenChange = false;
644 dispatchFormControlChangeEvent();
648 void HTMLSelectElement::scrollToSelection()
653 if (RenderObject* renderer = this->renderer())
654 toRenderListBox(renderer)->selectionChanged();
657 void HTMLSelectElement::setOptionsChangedOnRenderer()
659 if (RenderObject* renderer = this->renderer()) {
661 toRenderMenuList(renderer)->setOptionsChanged(true);
663 toRenderListBox(renderer)->setOptionsChanged(true);
667 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
669 if (m_shouldRecalcListItems)
673 Vector<HTMLElement*> items = m_listItems;
674 recalcListItems(false);
675 ASSERT(items == m_listItems);
682 void HTMLSelectElement::invalidateSelectedItems()
684 if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
685 collection->invalidateCache();
688 void HTMLSelectElement::setRecalcListItems()
690 // FIXME: This function does a bunch of confusing things depending on if it
691 // is in the document or not.
693 m_shouldRecalcListItems = true;
694 // Manual selection anchor is reset when manipulating the select programmatically.
695 m_activeSelectionAnchorIndex = -1;
696 setOptionsChangedOnRenderer();
697 setNeedsStyleRecalc(SubtreeStyleChange);
699 if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
700 collection->invalidateCache();
703 invalidateSelectedItems();
706 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
707 cache->childrenChanged(this);
711 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
715 m_shouldRecalcListItems = false;
717 HTMLOptionElement* foundSelected = 0;
718 HTMLOptionElement* firstOption = 0;
719 for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
720 if (!currentElement->isHTMLElement()) {
721 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
724 HTMLElement& current = toHTMLElement(*currentElement);
726 // optgroup tags may not nest. However, both FireFox and IE will
727 // flatten the tree automatically, so we follow suit.
728 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
729 if (current.hasTagName(optgroupTag)) {
730 m_listItems.append(¤t);
731 if (Element* nextElement = ElementTraversal::firstWithin(current)) {
732 currentElement = nextElement;
737 if (current.hasTagName(optionTag)) {
738 m_listItems.append(¤t);
740 if (updateSelectedStates && !m_multiple) {
741 HTMLOptionElement& option = toHTMLOptionElement(current);
743 firstOption = &option;
744 if (option.selected()) {
746 foundSelected->setSelectedState(false);
747 foundSelected = &option;
748 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
749 foundSelected = &option;
750 foundSelected->setSelectedState(true);
755 if (current.hasTagName(hrTag))
756 m_listItems.append(¤t);
758 // In conforming HTML code, only <optgroup> and <option> will be found
759 // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
760 // into those tags that we choose to. For web-compat, we should cope
761 // with the case where odd tags like a <div> have been added but we
762 // handle this because such tags have already been removed from the
763 // <select>'s subtree at this point.
764 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
767 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
768 firstOption->setSelectedState(true);
771 int HTMLSelectElement::selectedIndex() const
775 // Return the number of the first option selected.
776 const Vector<HTMLElement*>& items = listItems();
777 for (size_t i = 0; i < items.size(); ++i) {
778 HTMLElement* element = items[i];
779 if (element->hasTagName(optionTag)) {
780 if (toHTMLOptionElement(element)->selected())
789 void HTMLSelectElement::setSelectedIndex(int index)
791 selectOption(index, DeselectOtherOptions);
794 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
796 ASSERT(option->ownerSelectElement() == this);
797 if (optionIsSelected)
798 selectOption(option->index());
799 else if (!usesMenuList() || multiple())
802 selectOption(nextSelectableListIndex(-1));
805 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
807 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
809 const Vector<HTMLElement*>& items = listItems();
810 int listIndex = optionToListIndex(optionIndex);
812 HTMLElement* element = 0;
813 if (listIndex >= 0) {
814 element = items[listIndex];
815 if (element->hasTagName(optionTag)) {
816 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
817 setActiveSelectionAnchorIndex(listIndex);
818 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
819 setActiveSelectionEndIndex(listIndex);
820 toHTMLOptionElement(element)->setSelectedState(true);
825 deselectItemsWithoutValidation(element);
827 // For the menu list case, this is what makes the selected element appear.
828 if (RenderObject* renderer = this->renderer())
829 renderer->updateFromElement();
833 if (usesMenuList()) {
834 m_isProcessingUserDrivenChange = flags & UserDriven;
835 if (flags & DispatchChangeEvent)
836 dispatchChangeEventForMenuList();
837 if (RenderObject* renderer = this->renderer()) {
839 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
840 else if (renderer->isListBox())
841 toRenderListBox(renderer)->selectionChanged();
845 setNeedsValidityCheck();
846 notifyFormStateChanged();
849 int HTMLSelectElement::optionToListIndex(int optionIndex) const
851 const Vector<HTMLElement*>& items = listItems();
852 int listSize = static_cast<int>(items.size());
853 if (optionIndex < 0 || optionIndex >= listSize)
856 int optionIndex2 = -1;
857 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
858 if (items[listIndex]->hasTagName(optionTag)) {
860 if (optionIndex2 == optionIndex)
868 int HTMLSelectElement::listToOptionIndex(int listIndex) const
870 const Vector<HTMLElement*>& items = listItems();
871 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag))
874 // Actual index of option not counting OPTGROUP entries that may be in list.
876 for (int i = 0; i < listIndex; ++i) {
877 if (items[i]->hasTagName(optionTag))
884 void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
886 // Save the selection so it can be compared to the new selection when
887 // dispatching change events during blur event dispatch.
890 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, type);
893 void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
895 // We only need to fire change events here for menu lists, because we fire
896 // change events for list boxes whenever the selection change is actually made.
897 // This matches other browsers' behavior.
899 dispatchChangeEventForMenuList();
900 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
903 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
905 const Vector<HTMLElement*>& items = listItems();
906 for (unsigned i = 0; i < items.size(); ++i) {
907 HTMLElement* element = items[i];
908 if (element != excludeElement && element->hasTagName(optionTag))
909 toHTMLOptionElement(element)->setSelectedState(false);
913 FormControlState HTMLSelectElement::saveFormControlState() const
915 const Vector<HTMLElement*>& items = listItems();
916 size_t length = items.size();
917 FormControlState state;
918 for (unsigned i = 0; i < length; ++i) {
919 if (!items[i]->hasTagName(optionTag))
921 HTMLOptionElement* option = toHTMLOptionElement(items[i]);
922 if (!option->selected())
924 state.append(option->value());
931 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
933 const Vector<HTMLElement*>& items = listItems();
934 size_t loopEndIndex = std::min(items.size(), listIndexEnd);
935 for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
936 if (!items[i]->hasLocalName(optionTag))
938 if (toHTMLOptionElement(items[i])->value() == value)
944 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
948 const Vector<HTMLElement*>& items = listItems();
949 size_t itemsSize = items.size();
953 for (size_t i = 0; i < itemsSize; ++i) {
954 if (!items[i]->hasLocalName(optionTag))
956 toHTMLOptionElement(items[i])->setSelectedState(false);
960 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
961 if (foundIndex != kNotFound)
962 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
964 size_t startIndex = 0;
965 for (size_t i = 0; i < state.valueSize(); ++i) {
966 const String& value = state[i];
967 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
968 if (foundIndex == kNotFound)
969 foundIndex = searchOptionsForValue(value, 0, startIndex);
970 if (foundIndex == kNotFound)
972 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
973 startIndex = foundIndex + 1;
977 setOptionsChangedOnRenderer();
978 setNeedsValidityCheck();
981 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
983 bool oldUsesMenuList = usesMenuList();
984 m_multiple = !value.isNull();
985 setNeedsValidityCheck();
986 if (oldUsesMenuList != usesMenuList())
987 lazyReattachIfAttached();
990 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
992 const AtomicString& name = this->name();
996 bool successful = false;
997 const Vector<HTMLElement*>& items = listItems();
999 for (unsigned i = 0; i < items.size(); ++i) {
1000 HTMLElement* element = items[i];
1001 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->isDisabledFormControl()) {
1002 list.appendData(name, toHTMLOptionElement(element)->value());
1007 // It's possible that this is a menulist with multiple options and nothing
1008 // will be submitted (!successful). We won't send a unselected non-disabled
1009 // option as fallback. This behavior matches to other browsers.
1013 void HTMLSelectElement::resetImpl()
1015 HTMLOptionElement* firstOption = 0;
1016 HTMLOptionElement* selectedOption = 0;
1018 const Vector<HTMLElement*>& items = listItems();
1019 for (unsigned i = 0; i < items.size(); ++i) {
1020 HTMLElement* element = items[i];
1021 if (!element->hasTagName(optionTag))
1024 if (items[i]->fastHasAttribute(selectedAttr)) {
1025 if (selectedOption && !m_multiple)
1026 selectedOption->setSelectedState(false);
1027 toHTMLOptionElement(element)->setSelectedState(true);
1028 selectedOption = toHTMLOptionElement(element);
1030 toHTMLOptionElement(element)->setSelectedState(false);
1033 firstOption = toHTMLOptionElement(element);
1036 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
1037 firstOption->setSelectedState(true);
1039 setOptionsChangedOnRenderer();
1040 setNeedsStyleRecalc(SubtreeStyleChange);
1041 setNeedsValidityCheck();
1045 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
1047 if (!RenderTheme::theme().popsMenuByArrowKeys())
1050 if (!isSpatialNavigationEnabled(document().frame())) {
1051 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
1053 // Calling focus() may cause us to lose our renderer. Return true so
1054 // that our caller doesn't process the event further, but don't set
1055 // the event as handled.
1059 // Save the selection so it can be compared to the new selection
1060 // when dispatching change events during selectOption, which
1061 // gets called from RenderMenuList::valueChanged, which gets called
1062 // after the user makes a selection from the menu.
1063 saveLastSelection();
1064 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1065 menuList->showPopup();
1066 event->setDefaultHandled();
1075 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1077 RenderTheme& renderTheme = RenderTheme::theme();
1079 if (event->type() == EventTypeNames::keydown) {
1080 if (!renderer() || !event->isKeyboardEvent())
1083 if (platformHandleKeydownEvent(toKeyboardEvent(event)))
1086 // When using spatial navigation, we want to be able to navigate away
1087 // from the select element when the user hits any of the arrow keys,
1088 // instead of changing the selection.
1089 if (isSpatialNavigationEnabled(document().frame())) {
1090 if (!m_activeSelectionState)
1094 const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
1095 bool handled = true;
1096 const Vector<HTMLElement*>& listItems = this->listItems();
1097 int listIndex = optionToListIndex(selectedIndex());
1099 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1100 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1101 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1102 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1103 else if (keyIdentifier == "PageDown")
1104 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1105 else if (keyIdentifier == "PageUp")
1106 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1107 else if (keyIdentifier == "Home")
1108 listIndex = nextValidIndex(-1, SkipForwards, 1);
1109 else if (keyIdentifier == "End")
1110 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1114 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1115 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1118 event->setDefaultHandled();
1121 // Use key press event here since sending simulated mouse events
1122 // on key down blocks the proper sending of the key press event.
1123 if (event->type() == EventTypeNames::keypress) {
1124 if (!renderer() || !event->isKeyboardEvent())
1127 int keyCode = toKeyboardEvent(event)->keyCode();
1128 bool handled = false;
1130 if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1131 // Use space to toggle arrow key handling for selection change or spatial navigation.
1132 m_activeSelectionState = !m_activeSelectionState;
1133 event->setDefaultHandled();
1137 if (renderTheme.popsMenuBySpaceOrReturn()) {
1138 if (keyCode == ' ' || keyCode == '\r') {
1141 // Calling focus() may remove the renderer or change the
1143 if (!renderer() || !renderer()->isMenuList())
1146 // Save the selection so it can be compared to the new selection
1147 // when dispatching change events during selectOption, which
1148 // gets called from RenderMenuList::valueChanged, which gets called
1149 // after the user makes a selection from the menu.
1150 saveLastSelection();
1151 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1152 menuList->showPopup();
1155 } else if (renderTheme.popsMenuByArrowKeys()) {
1156 if (keyCode == ' ') {
1159 // Calling focus() may remove the renderer or change the
1161 if (!renderer() || !renderer()->isMenuList())
1164 // Save the selection so it can be compared to the new selection
1165 // when dispatching change events during selectOption, which
1166 // gets called from RenderMenuList::valueChanged, which gets called
1167 // after the user makes a selection from the menu.
1168 saveLastSelection();
1169 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1170 menuList->showPopup();
1172 } else if (keyCode == '\r') {
1174 form()->submitImplicitly(event, false);
1175 dispatchChangeEventForMenuList();
1181 event->setDefaultHandled();
1184 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1186 if (renderer() && renderer()->isMenuList()) {
1187 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1188 if (menuList->popupIsVisible())
1189 menuList->hidePopup();
1191 // Save the selection so it can be compared to the new
1192 // selection when we call onChange during selectOption,
1193 // which gets called from RenderMenuList::valueChanged,
1194 // which gets called after the user makes a selection from
1196 saveLastSelection();
1197 menuList->showPopup();
1201 event->setDefaultHandled();
1204 if (event->type() == EventTypeNames::blur) {
1205 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1206 if (menuList->popupIsVisible())
1207 menuList->hidePopup();
1212 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1214 ASSERT(listIndex >= 0);
1216 // Save the selection so it can be compared to the new selection when
1217 // dispatching change events during mouseup, or after autoscroll finishes.
1218 saveLastSelection();
1220 m_activeSelectionState = true;
1222 bool shiftSelect = m_multiple && shift;
1223 bool multiSelect = m_multiple && multi && !shift;
1225 HTMLElement* clickedElement = listItems()[listIndex];
1226 if (clickedElement->hasTagName(optionTag)) {
1227 // Keep track of whether an active selection (like during drag
1228 // selection), should select or deselect.
1229 if (toHTMLOptionElement(clickedElement)->selected() && multiSelect)
1230 m_activeSelectionState = false;
1231 if (!m_activeSelectionState)
1232 toHTMLOptionElement(clickedElement)->setSelectedState(false);
1235 // If we're not in any special multiple selection mode, then deselect all
1236 // other items, excluding the clicked option. If no option was clicked, then
1237 // this will deselect all items in the list.
1238 if (!shiftSelect && !multiSelect)
1239 deselectItemsWithoutValidation(clickedElement);
1241 // If the anchor hasn't been set, and we're doing a single selection or a
1242 // shift selection, then initialize the anchor to the first selected index.
1243 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1244 setActiveSelectionAnchorIndex(selectedIndex());
1246 // Set the selection state of the clicked option.
1247 if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->isDisabledFormControl())
1248 toHTMLOptionElement(clickedElement)->setSelectedState(true);
1250 // If there was no selectedIndex() for the previous initialization, or If
1251 // we're doing a single selection, or a multiple selection (using cmd or
1252 // ctrl), then initialize the anchor index to the listIndex that just got
1254 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1255 setActiveSelectionAnchorIndex(listIndex);
1257 setActiveSelectionEndIndex(listIndex);
1258 updateListBoxSelection(!multiSelect);
1261 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1263 const Vector<HTMLElement*>& listItems = this->listItems();
1264 bool dragSelection = false;
1265 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1267 // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
1271 // Convert to coords relative to the list box if needed.
1272 MouseEvent* mouseEvent = toMouseEvent(event);
1273 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
1274 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
1275 if (listIndex >= 0) {
1276 if (!isDisabledFormControl()) {
1278 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
1280 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
1283 if (Frame* frame = document().frame())
1284 frame->eventHandler().setMouseDownMayStartAutoscroll();
1286 event->setDefaultHandled();
1288 } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
1289 MouseEvent* mouseEvent = toMouseEvent(event);
1290 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
1293 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
1294 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
1295 if (listIndex >= 0) {
1296 if (!isDisabledFormControl()) {
1298 // Only extend selection if there is something selected.
1299 if (m_activeSelectionAnchorIndex < 0)
1302 setActiveSelectionEndIndex(listIndex);
1303 updateListBoxSelection(false);
1305 setActiveSelectionAnchorIndex(listIndex);
1306 setActiveSelectionEndIndex(listIndex);
1307 updateListBoxSelection(true);
1310 dragSelection = true;
1312 } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
1313 // We didn't start this click/drag on any options.
1314 if (m_lastOnChangeSelection.isEmpty())
1316 // This makes sure we fire dispatchFormControlChangeEvent for a single
1317 // click. For drag selection, onChange will fire when the autoscroll
1319 if (!dragSelection) {
1322 } else if (event->type() == EventTypeNames::keydown) {
1323 if (!event->isKeyboardEvent())
1325 const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
1327 bool handled = false;
1329 if (m_activeSelectionEndIndex < 0) {
1330 // Initialize the end index
1331 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1332 int startIndex = lastSelectedListIndex();
1334 if (keyIdentifier == "Down")
1335 endIndex = nextSelectableListIndex(startIndex);
1337 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1338 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1339 int startIndex = optionToListIndex(selectedIndex());
1341 if (keyIdentifier == "Up")
1342 endIndex = previousSelectableListIndex(startIndex);
1344 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1347 // Set the end index based on the current end index.
1348 if (keyIdentifier == "Down") {
1349 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1351 } else if (keyIdentifier == "Up") {
1352 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1354 } else if (keyIdentifier == "PageDown") {
1355 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1357 } else if (keyIdentifier == "PageUp") {
1358 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1362 if (keyIdentifier == "Home") {
1363 endIndex = firstSelectableListIndex();
1365 } else if (keyIdentifier == "End") {
1366 endIndex = lastSelectableListIndex();
1370 if (isSpatialNavigationEnabled(document().frame()))
1371 // Check if the selection moves to the boundary.
1372 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1375 if (endIndex >= 0 && handled) {
1376 // Save the selection so it can be compared to the new selection
1377 // when dispatching change events immediately after making the new
1379 saveLastSelection();
1381 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1382 setActiveSelectionEndIndex(endIndex);
1384 bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
1386 m_activeSelectionState = true;
1387 // If the anchor is unitialized, or if we're going to deselect all
1388 // other options, then set the anchor index equal to the end index.
1389 bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
1390 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1392 deselectItemsWithoutValidation();
1393 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1396 toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
1397 if (selectNewItem) {
1398 updateListBoxSelection(deselectOthers);
1401 scrollToSelection();
1403 event->setDefaultHandled();
1405 } else if (event->type() == EventTypeNames::keypress) {
1406 if (!event->isKeyboardEvent())
1408 int keyCode = toKeyboardEvent(event)->keyCode();
1410 if (keyCode == '\r') {
1412 form()->submitImplicitly(event, false);
1413 event->setDefaultHandled();
1414 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1415 // Use space to toggle selection change.
1416 m_activeSelectionState = !m_activeSelectionState;
1417 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
1419 event->setDefaultHandled();
1424 void HTMLSelectElement::defaultEventHandler(Event* event)
1429 if (isDisabledFormControl()) {
1430 HTMLFormControlElementWithState::defaultEventHandler(event);
1435 menuListDefaultEventHandler(event);
1437 listBoxDefaultEventHandler(event);
1438 if (event->defaultHandled())
1441 if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
1442 KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
1443 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
1444 typeAheadFind(keyboardEvent);
1445 event->setDefaultHandled();
1449 HTMLFormControlElementWithState::defaultEventHandler(event);
1452 int HTMLSelectElement::lastSelectedListIndex() const
1454 const Vector<HTMLElement*>& items = listItems();
1455 for (size_t i = items.size(); i;) {
1456 HTMLElement* element = items[--i];
1457 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
1463 int HTMLSelectElement::indexOfSelectedOption() const
1465 return optionToListIndex(selectedIndex());
1468 int HTMLSelectElement::optionCount() const
1470 return listItems().size();
1473 String HTMLSelectElement::optionAtIndex(int index) const
1475 const Vector<HTMLElement*>& items = listItems();
1477 HTMLElement* element = items[index];
1478 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
1480 return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
1483 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
1485 int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
1488 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1489 if (!usesMenuList())
1493 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
1495 // When the element is created during document parsing, it won't have any
1496 // items yet - but for innerHTML and related methods, this method is called
1497 // after the whole subtree is constructed.
1499 HTMLFormControlElementWithState::insertedInto(insertionPoint);
1500 return InsertionDone;
1503 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1505 // First bring into focus the list box.
1507 accessKeyAction(false);
1509 // If this index is already selected, unselect. otherwise update the selected index.
1510 const Vector<HTMLElement*>& items = listItems();
1511 int listIndex = optionToListIndex(index);
1512 if (listIndex >= 0) {
1513 HTMLElement* element = items[listIndex];
1514 if (element->hasTagName(optionTag)) {
1515 if (toHTMLOptionElement(element)->selected())
1516 toHTMLOptionElement(element)->setSelectedState(false);
1518 selectOption(index, DispatchChangeEvent | UserDriven);
1523 dispatchChangeEventForMenuList();
1527 scrollToSelection();
1530 unsigned HTMLSelectElement::length() const
1532 unsigned options = 0;
1534 const Vector<HTMLElement*>& items = listItems();
1535 for (unsigned i = 0; i < items.size(); ++i) {
1536 if (items[i]->hasTagName(optionTag))
1543 void HTMLSelectElement::finishParsingChildren()
1545 HTMLFormControlElementWithState::finishParsingChildren();
1546 updateListItemSelectedStates();
1549 bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
1551 if (!value) { // undefined or null
1555 setOption(index, value.get(), exceptionState);
1559 bool HTMLSelectElement::isInteractiveContent() const
1564 bool HTMLSelectElement::supportsAutofocus() const