2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
3 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
4 * (C) 1999 Antti Koivisto (koivisto@kde.org)
5 * (C) 2001 Dirk Mueller (mueller@kde.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights reserved.
7 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
8 * Copyright (C) 2010 Google Inc. All rights reserved.
9 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB. If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
29 #include "HTMLSelectElement.h"
31 #include "AXObjectCache.h"
32 #include "Attribute.h"
34 #include "ChromeClient.h"
35 #include "EventNames.h"
36 #include "FormDataList.h"
38 #include "HTMLFormElement.h"
39 #include "HTMLNames.h"
40 #include "HTMLOptGroupElement.h"
41 #include "HTMLOptionElement.h"
42 #include "HTMLOptionsCollection.h"
43 #include "KeyboardEvent.h"
44 #include "MouseEvent.h"
46 #include "RenderListBox.h"
47 #include "RenderMenuList.h"
48 #include "ScriptEventListener.h"
49 #include "SpatialNavigation.h"
50 #include <wtf/text/StringBuilder.h>
51 #include <wtf/unicode/Unicode.h>
54 using namespace WTF::Unicode;
58 using namespace HTMLNames;
60 // Upper limit agreed upon with representatives of Opera and Mozilla.
61 static const unsigned maxSelectItems = 10000;
63 // Configure platform-specific behavior when focused pop-up receives arrow/space/return keystroke.
64 // (PLATFORM(MAC) and PLATFORM(GTK) are always false in Chromium, hence the extra tests.)
65 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
66 #define ARROW_KEYS_POP_MENU 1
67 #define SPACE_OR_RETURN_POP_MENU 0
68 #elif PLATFORM(GTK) || (PLATFORM(CHROMIUM) && OS(UNIX))
69 #define ARROW_KEYS_POP_MENU 0
70 #define SPACE_OR_RETURN_POP_MENU 1
72 #define ARROW_KEYS_POP_MENU 0
73 #define SPACE_OR_RETURN_POP_MENU 0
76 static const DOMTimeStamp typeAheadTimeout = 1000;
78 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
79 : HTMLFormControlElementWithState(tagName, document, form)
82 , m_lastOnChangeIndex(-1)
83 , m_activeSelectionAnchorIndex(-1)
84 , m_activeSelectionEndIndex(-1)
86 , m_isProcessingUserDrivenChange(false)
88 , m_activeSelectionState(false)
89 , m_shouldRecalcListItems(false)
91 ASSERT(hasTagName(selectTag));
94 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
96 ASSERT(tagName.matches(selectTag));
97 return adoptRef(new HTMLSelectElement(tagName, document, form));
100 const AtomicString& HTMLSelectElement::formControlType() const
102 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple"));
103 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one"));
104 return m_multiple ? selectMultiple : selectOne;
107 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
109 deselectItemsWithoutValidation(excludeElement);
110 setNeedsValidityCheck();
113 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
115 // User interaction such as mousedown events can cause list box select elements to send change events.
116 // This produces that same behavior for changes triggered by other code running on behalf of the user.
117 if (!usesMenuList()) {
118 updateSelectedState(optionIndex, allowMultipleSelection, false);
119 setNeedsValidityCheck();
125 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
126 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
127 // The selectOption function does not behave this way, possibly because other callers need a change event even
128 // in cases where the selected option is not change.
129 if (optionIndex == selectedIndex())
132 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
135 bool HTMLSelectElement::hasPlaceholderLabelOption() const
137 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
139 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
140 // Using "size() > 1" here because size() may be 0 in WebKit.
141 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
143 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
144 // In this case, the display size should be assumed as the default.
145 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
147 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
148 if (multiple() || size() > 1)
151 int listIndex = optionToListIndex(0);
152 ASSERT(listIndex >= 0);
155 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]);
156 return !listIndex && option->value().isEmpty();
159 bool HTMLSelectElement::valueMissing() const
161 if (!isRequiredFormControl())
164 int firstSelectionIndex = selectedIndex();
166 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
167 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
170 #if ENABLE(NO_LISTBOX_RENDERING)
171 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
174 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
176 updateSelectedState(listIndex, allowMultiplySelections, shift);
177 setNeedsValidityCheck();
184 int HTMLSelectElement::activeSelectionStartListIndex() const
186 if (m_activeSelectionAnchorIndex >= 0)
187 return m_activeSelectionAnchorIndex;
188 return optionToListIndex(selectedIndex());
191 int HTMLSelectElement::activeSelectionEndListIndex() const
193 if (m_activeSelectionEndIndex >= 0)
194 return m_activeSelectionEndIndex;
195 return lastSelectedListIndex();
198 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec)
200 // Make sure the element is ref'd and deref'd so we don't leak it.
201 RefPtr<HTMLElement> protectNewChild(element);
203 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
206 insertBefore(element, before, ec);
207 setNeedsValidityCheck();
210 void HTMLSelectElement::remove(int optionIndex)
212 int listIndex = optionToListIndex(optionIndex);
217 listItems()[listIndex]->remove(ec);
220 void HTMLSelectElement::remove(HTMLOptionElement* option)
222 if (option->ownerSelectElement() != this)
229 String HTMLSelectElement::value() const
231 const Vector<HTMLElement*>& items = listItems();
232 for (unsigned i = 0; i < items.size(); i++) {
233 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
234 return static_cast<HTMLOptionElement*>(items[i])->value();
239 void HTMLSelectElement::setValue(const String &value)
243 // find the option with value() matching the given parameter
244 // and make it the current selection.
245 const Vector<HTMLElement*>& items = listItems();
246 unsigned optionIndex = 0;
247 for (unsigned i = 0; i < items.size(); i++) {
248 if (items[i]->hasLocalName(optionTag)) {
249 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
250 setSelectedIndex(optionIndex);
258 void HTMLSelectElement::parseMappedAttribute(Attribute* attr)
260 if (attr->name() == sizeAttr) {
261 int oldSize = m_size;
262 // Set the attribute value to a number.
263 // This is important since the style rules for this attribute can determine the appearance property.
264 int size = attr->value().toInt();
265 String attrSize = String::number(size);
266 if (attrSize != attr->value())
267 attr->setValue(attrSize);
270 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
272 updateListItemSelectedStates();
275 setNeedsValidityCheck();
276 if (m_size != oldSize && attached()) {
278 setRecalcListItems();
280 } else if (attr->name() == multipleAttr)
281 parseMultipleAttribute(attr);
282 else if (attr->name() == accesskeyAttr) {
283 // FIXME: ignore for the moment.
284 } else if (attr->name() == alignAttr) {
285 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
286 // See http://bugs.webkit.org/show_bug.cgi?id=12072
287 } else if (attr->name() == onchangeAttr)
288 setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
290 HTMLFormControlElementWithState::parseMappedAttribute(attr);
293 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
296 return isFocusable();
297 return HTMLFormControlElementWithState::isKeyboardFocusable(event);
300 bool HTMLSelectElement::isMouseFocusable() const
303 return isFocusable();
304 return HTMLFormControlElementWithState::isMouseFocusable();
307 bool HTMLSelectElement::canSelectAll() const
309 return !usesMenuList();
312 RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
315 return new (arena) RenderMenuList(this);
316 return new (arena) RenderListBox(this);
319 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
321 return HTMLOptionsCollection::create(this);
324 void HTMLSelectElement::updateListItemSelectedStates()
326 if (m_shouldRecalcListItems)
330 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
332 setRecalcListItems();
333 setNeedsValidityCheck();
335 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
337 if (AXObjectCache::accessibilityEnabled() && renderer())
338 renderer()->document()->axObjectCache()->childrenChanged(renderer());
341 void HTMLSelectElement::optionElementChildrenChanged()
343 setRecalcListItems();
344 setNeedsValidityCheck();
346 if (AXObjectCache::accessibilityEnabled() && renderer())
347 renderer()->document()->axObjectCache()->childrenChanged(renderer());
350 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
353 dispatchSimulatedClick(0, sendMouseEvents);
356 void HTMLSelectElement::setMultiple(bool multiple)
358 bool oldMultiple = this->multiple();
359 int oldSelectedIndex = selectedIndex();
360 setAttribute(multipleAttr, multiple ? "" : 0);
362 // Restore selectedIndex after changing the multiple flag to preserve
363 // selection as single-line and multi-line has different defaults.
364 if (oldMultiple != this->multiple())
365 setSelectedIndex(oldSelectedIndex);
368 void HTMLSelectElement::setSize(int size)
370 setAttribute(sizeAttr, String::number(size));
373 Node* HTMLSelectElement::namedItem(const AtomicString& name)
375 return options()->namedItem(name);
378 Node* HTMLSelectElement::item(unsigned index)
380 return options()->item(index);
383 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
386 if (index > maxSelectItems - 1)
387 index = maxSelectItems - 1;
388 int diff = index - length();
389 HTMLElement* before = 0;
390 // Out of array bounds? First insert empty dummies.
392 setLength(index, ec);
393 // Replace an existing entry?
394 } else if (diff < 0) {
395 before = toHTMLElement(options()->item(index+1));
398 // Finally add the new element.
400 add(option, before, ec);
401 if (diff >= 0 && option->selected())
402 optionSelectionStateChanged(option, true);
406 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
409 if (newLen > maxSelectItems)
410 newLen = maxSelectItems;
411 int diff = length() - newLen;
413 if (diff < 0) { // Add dummy elements.
415 RefPtr<Element> option = document()->createElement(optionTag, false);
417 add(toHTMLElement(option.get()), 0, ec);
422 const Vector<HTMLElement*>& items = listItems();
424 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
425 // of elements that we intend to remove then attempt to remove them one at a time.
426 Vector<RefPtr<Element> > itemsToRemove;
427 size_t optionIndex = 0;
428 for (size_t i = 0; i < items.size(); ++i) {
429 Element* item = items[i];
430 if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
431 ASSERT(item->parentNode());
432 itemsToRemove.append(item);
436 for (size_t i = 0; i < itemsToRemove.size(); ++i) {
437 Element* item = itemsToRemove[i].get();
438 if (item->parentNode())
439 item->parentNode()->removeChild(item, ec);
442 setNeedsValidityCheck();
445 bool HTMLSelectElement::isRequiredFormControl() const
450 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
451 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
452 // Otherwise, it returns |listIndex|.
453 // Valid means that it is enabled and an option element.
454 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
456 ASSERT(direction == -1 || direction == 1);
457 const Vector<HTMLElement*>& listItems = this->listItems();
458 int lastGoodIndex = listIndex;
459 int size = listItems.size();
460 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
462 if (!listItems[listIndex]->disabled() && listItems[listIndex]->hasTagName(optionTag)) {
463 lastGoodIndex = listIndex;
468 return lastGoodIndex;
471 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
473 return nextValidIndex(startIndex, SkipForwards, 1);
476 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
478 if (startIndex == -1)
479 startIndex = listItems().size();
480 return nextValidIndex(startIndex, SkipBackwards, 1);
483 int HTMLSelectElement::firstSelectableListIndex() const
485 const Vector<HTMLElement*>& items = listItems();
486 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
487 if (static_cast<size_t>(index) == items.size())
492 int HTMLSelectElement::lastSelectableListIndex() const
494 return nextValidIndex(-1, SkipForwards, INT_MAX);
497 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
498 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
500 const Vector<HTMLElement*>& items = listItems();
501 // Can't use m_size because renderer forces a minimum size.
503 if (renderer()->isListBox())
504 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
506 // One page away, but not outside valid bounds.
507 // If there is a valid option item one page away, the index is chosen.
508 // If there is no exact one page away valid option, returns startIndex or the most far index.
509 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
510 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
511 return nextValidIndex(edgeIndex, direction, skipAmount);
514 void HTMLSelectElement::selectAll()
516 ASSERT(!usesMenuList());
517 if (!renderer() || !m_multiple)
520 // Save the selection so it can be compared to the new selectAll selection
521 // when dispatching change events.
524 m_activeSelectionState = true;
525 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
526 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
528 updateListBoxSelection(false);
530 setNeedsValidityCheck();
533 void HTMLSelectElement::saveLastSelection()
535 if (usesMenuList()) {
536 m_lastOnChangeIndex = selectedIndex();
540 m_lastOnChangeSelection.clear();
541 const Vector<HTMLElement*>& items = listItems();
542 for (unsigned i = 0; i < items.size(); ++i) {
543 HTMLElement* element = items[i];
544 m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
548 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
550 m_activeSelectionAnchorIndex = index;
552 // Cache the selection state so we can restore the old selection as the new
553 // selection pivots around this anchor index.
554 m_cachedStateForActiveSelection.clear();
556 const Vector<HTMLElement*>& items = listItems();
557 for (unsigned i = 0; i < items.size(); ++i) {
558 HTMLElement* element = items[i];
559 m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
563 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
565 m_activeSelectionEndIndex = index;
568 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
570 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
571 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
573 unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
574 unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
576 const Vector<HTMLElement*>& items = listItems();
577 for (unsigned i = 0; i < items.size(); ++i) {
578 HTMLElement* element = items[i];
579 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->disabled())
582 if (i >= start && i <= end)
583 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
584 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
585 toHTMLOptionElement(element)->setSelectedState(false);
587 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
591 setNeedsValidityCheck();
594 void HTMLSelectElement::listBoxOnChange()
596 ASSERT(!usesMenuList() || m_multiple);
598 const Vector<HTMLElement*>& items = listItems();
600 // If the cached selection list is empty, or the size has changed, then fire
601 // dispatchFormControlChangeEvent, and return early.
602 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
603 dispatchFormControlChangeEvent();
607 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
608 bool fireOnChange = false;
609 for (unsigned i = 0; i < items.size(); ++i) {
610 HTMLElement* element = items[i];
611 bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
612 if (selected != m_lastOnChangeSelection[i])
614 m_lastOnChangeSelection[i] = selected;
618 dispatchFormControlChangeEvent();
621 void HTMLSelectElement::dispatchChangeEventForMenuList()
623 ASSERT(usesMenuList());
625 int selected = selectedIndex();
626 if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
627 m_lastOnChangeIndex = selected;
628 m_isProcessingUserDrivenChange = false;
629 dispatchFormControlChangeEvent();
633 void HTMLSelectElement::scrollToSelection()
638 if (RenderObject* renderer = this->renderer())
639 toRenderListBox(renderer)->selectionChanged();
642 void HTMLSelectElement::setOptionsChangedOnRenderer()
644 if (RenderObject* renderer = this->renderer()) {
646 toRenderMenuList(renderer)->setOptionsChanged(true);
648 toRenderListBox(renderer)->setOptionsChanged(true);
652 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
654 if (m_shouldRecalcListItems)
658 Vector<HTMLElement*> items = m_listItems;
659 recalcListItems(false);
660 ASSERT(items == m_listItems);
667 void HTMLSelectElement::setRecalcListItems()
669 m_shouldRecalcListItems = true;
670 // Manual selection anchor is reset when manipulating the select programmatically.
671 m_activeSelectionAnchorIndex = -1;
672 setOptionsChangedOnRenderer();
673 setNeedsStyleRecalc();
675 m_collectionInfo.reset();
678 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
682 m_shouldRecalcListItems = false;
684 HTMLOptionElement* foundSelected = 0;
685 for (Node* currentNode = this->firstChild(); currentNode;) {
686 if (!currentNode->isHTMLElement()) {
687 currentNode = currentNode->traverseNextSibling(this);
691 HTMLElement* current = toHTMLElement(currentNode);
693 // optgroup tags may not nest. However, both FireFox and IE will
694 // flatten the tree automatically, so we follow suit.
695 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
696 if (current->hasTagName(optgroupTag)) {
697 m_listItems.append(current);
698 if (current->firstChild()) {
699 currentNode = current->firstChild();
704 if (current->hasTagName(optionTag)) {
705 m_listItems.append(current);
707 if (updateSelectedStates && !m_multiple) {
708 if (!foundSelected && (m_size <= 1 || toHTMLOptionElement(current)->selected())) {
709 foundSelected = toHTMLOptionElement(current);
710 foundSelected->setSelectedState(true);
711 } else if (foundSelected && toHTMLOptionElement(current)->selected()) {
712 foundSelected->setSelectedState(false);
713 foundSelected = toHTMLOptionElement(current);
718 if (current->hasTagName(hrTag))
719 m_listItems.append(current);
721 // In conforming HTML code, only <optgroup> and <option> will be found
722 // within a <select>. We call traverseNextSibling so that we only step
723 // into those tags that we choose to. For web-compat, we should cope
724 // with the case where odd tags like a <div> have been added but we
725 // handle this because such tags have already been removed from the
726 // <select>'s subtree at this point.
727 currentNode = currentNode->traverseNextSibling(this);
731 int HTMLSelectElement::selectedIndex() const
735 // Return the number of the first option selected.
736 const Vector<HTMLElement*>& items = listItems();
737 for (size_t i = 0; i < items.size(); ++i) {
738 HTMLElement* element = items[i];
739 if (element->hasTagName(optionTag)) {
740 if (toHTMLOptionElement(element)->selected())
749 void HTMLSelectElement::setSelectedIndex(int index)
751 selectOption(index, DeselectOtherOptions);
754 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
756 ASSERT(option->ownerSelectElement() == this);
757 if (optionIsSelected)
758 selectOption(option->index());
760 selectOption(m_multiple ? -1 : nextSelectableListIndex(-1));
763 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
765 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
767 const Vector<HTMLElement*>& items = listItems();
768 int listIndex = optionToListIndex(optionIndex);
770 HTMLElement* element = 0;
771 if (listIndex >= 0) {
772 element = items[listIndex];
773 if (element->hasTagName(optionTag)) {
774 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
775 setActiveSelectionAnchorIndex(listIndex);
776 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
777 setActiveSelectionEndIndex(listIndex);
778 toHTMLOptionElement(element)->setSelectedState(true);
783 deselectItemsWithoutValidation(element);
785 // For the menu list case, this is what makes the selected element appear.
786 if (RenderObject* renderer = this->renderer())
787 renderer->updateFromElement();
791 if (usesMenuList()) {
792 m_isProcessingUserDrivenChange = flags & UserDriven;
793 if (flags & DispatchChangeEvent)
794 dispatchChangeEventForMenuList();
795 if (RenderObject* renderer = this->renderer()) {
797 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
798 else if (renderer->isListBox())
799 toRenderListBox(renderer)->selectionChanged();
803 setNeedsValidityCheck();
804 if (Frame* frame = document()->frame())
805 frame->page()->chrome()->client()->formStateDidChange(this);
808 int HTMLSelectElement::optionToListIndex(int optionIndex) const
810 const Vector<HTMLElement*>& items = listItems();
811 int listSize = static_cast<int>(items.size());
812 if (optionIndex < 0 || optionIndex >= listSize)
815 int optionIndex2 = -1;
816 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
817 if (items[listIndex]->hasTagName(optionTag)) {
819 if (optionIndex2 == optionIndex)
827 int HTMLSelectElement::listToOptionIndex(int listIndex) const
829 const Vector<HTMLElement*>& items = listItems();
830 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag))
833 // Actual index of option not counting OPTGROUP entries that may be in list.
835 for (int i = 0; i < listIndex; ++i) {
836 if (items[i]->hasTagName(optionTag))
843 void HTMLSelectElement::dispatchFocusEvent(PassRefPtr<Node> oldFocusedNode)
845 // Save the selection so it can be compared to the new selection when
846 // dispatching change events during blur event dispatch.
849 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedNode);
852 void HTMLSelectElement::dispatchBlurEvent(PassRefPtr<Node> newFocusedNode)
854 // We only need to fire change events here for menu lists, because we fire
855 // change events for list boxes whenever the selection change is actually made.
856 // This matches other browsers' behavior.
858 dispatchChangeEventForMenuList();
859 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedNode);
862 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
864 const Vector<HTMLElement*>& items = listItems();
865 for (unsigned i = 0; i < items.size(); ++i) {
866 HTMLElement* element = items[i];
867 if (element != excludeElement && element->hasTagName(optionTag))
868 toHTMLOptionElement(element)->setSelectedState(false);
872 bool HTMLSelectElement::saveFormControlState(String& value) const
874 const Vector<HTMLElement*>& items = listItems();
875 size_t length = items.size();
876 StringBuilder builder;
877 builder.reserveCapacity(length);
878 for (unsigned i = 0; i < length; ++i) {
879 HTMLElement* element = items[i];
880 bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
881 builder.append(selected ? 'X' : '.');
883 value = builder.toString();
887 void HTMLSelectElement::restoreFormControlState(const String& state)
891 const Vector<HTMLElement*>& items = listItems();
892 size_t length = items.size();
894 for (size_t i = 0; i < length; ++i) {
895 HTMLElement* element = items[i];
896 if (element->hasTagName(optionTag))
897 toHTMLOptionElement(element)->setSelectedState(state[i] == 'X');
900 setOptionsChangedOnRenderer();
901 setNeedsValidityCheck();
904 void HTMLSelectElement::parseMultipleAttribute(const Attribute* attribute)
906 bool oldUsesMenuList = usesMenuList();
907 m_multiple = !attribute->isNull();
908 setNeedsValidityCheck();
909 if (oldUsesMenuList != usesMenuList())
910 reattachIfAttached();
913 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
915 const AtomicString& name = formControlName();
919 bool successful = false;
920 const Vector<HTMLElement*>& items = listItems();
922 for (unsigned i = 0; i < items.size(); ++i) {
923 HTMLElement* element = items[i];
924 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->disabled()) {
925 list.appendData(name, toHTMLOptionElement(element)->value());
930 // It's possible that this is a menulist with multiple options and nothing
931 // will be submitted (!successful). We won't send a unselected non-disabled
932 // option as fallback. This behavior matches to other browsers.
936 void HTMLSelectElement::reset()
938 HTMLOptionElement* firstOption = 0;
939 HTMLOptionElement* selectedOption = 0;
941 const Vector<HTMLElement*>& items = listItems();
942 for (unsigned i = 0; i < items.size(); ++i) {
943 HTMLElement* element = items[i];
944 if (!element->hasTagName(optionTag))
947 if (items[i]->fastHasAttribute(selectedAttr)) {
948 if (selectedOption && !m_multiple)
949 selectedOption->setSelectedState(false);
950 toHTMLOptionElement(element)->setSelectedState(true);
951 selectedOption = toHTMLOptionElement(element);
953 toHTMLOptionElement(element)->setSelectedState(false);
956 firstOption = toHTMLOptionElement(element);
959 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
960 firstOption->setSelectedState(true);
962 setOptionsChangedOnRenderer();
963 setNeedsStyleRecalc();
964 setNeedsValidityCheck();
967 #if !PLATFORM(WIN) || OS(WINCE)
968 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
970 #if ARROW_KEYS_POP_MENU
971 if (!isSpatialNavigationEnabled(document()->frame())) {
972 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
974 // Calling focus() may cause us to lose our renderer. Return true so
975 // that our caller doesn't process the event further, but don't set
976 // the event as handled.
980 // Save the selection so it can be compared to the new selection
981 // when dispatching change events during selectOption, which
982 // gets called from RenderMenuList::valueChanged, which gets called
983 // after the user makes a selection from the menu.
985 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
986 menuList->showPopup();
987 event->setDefaultHandled();
998 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1000 if (event->type() == eventNames().keydownEvent) {
1001 if (!renderer() || !event->isKeyboardEvent())
1004 if (platformHandleKeydownEvent(static_cast<KeyboardEvent*>(event)))
1007 // When using spatial navigation, we want to be able to navigate away
1008 // from the select element when the user hits any of the arrow keys,
1009 // instead of changing the selection.
1010 if (isSpatialNavigationEnabled(document()->frame())) {
1011 if (!m_activeSelectionState)
1015 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
1016 bool handled = true;
1017 const Vector<HTMLElement*>& listItems = this->listItems();
1018 int listIndex = optionToListIndex(selectedIndex());
1020 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1021 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1022 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1023 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1024 else if (keyIdentifier == "PageDown")
1025 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1026 else if (keyIdentifier == "PageUp")
1027 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1028 else if (keyIdentifier == "Home")
1029 listIndex = nextValidIndex(-1, SkipForwards, 1);
1030 else if (keyIdentifier == "End")
1031 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1035 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1036 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | UserDriven);
1039 event->setDefaultHandled();
1042 // Use key press event here since sending simulated mouse events
1043 // on key down blocks the proper sending of the key press event.
1044 if (event->type() == eventNames().keypressEvent) {
1045 if (!renderer() || !event->isKeyboardEvent())
1048 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
1049 bool handled = false;
1051 if (keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) {
1052 // Use space to toggle arrow key handling for selection change or spatial navigation.
1053 m_activeSelectionState = !m_activeSelectionState;
1054 event->setDefaultHandled();
1058 #if SPACE_OR_RETURN_POP_MENU
1059 if (keyCode == ' ' || keyCode == '\r') {
1062 // Calling focus() may cause us to lose our renderer, in which case
1063 // do not want to handle the event.
1067 // Save the selection so it can be compared to the new selection
1068 // when dispatching change events during selectOption, which
1069 // gets called from RenderMenuList::valueChanged, which gets called
1070 // after the user makes a selection from the menu.
1071 saveLastSelection();
1072 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1073 menuList->showPopup();
1076 #elif ARROW_KEYS_POP_MENU
1077 if (keyCode == ' ') {
1080 // Calling focus() may cause us to lose our renderer, in which case
1081 // do not want to handle the event.
1085 // Save the selection so it can be compared to the new selection
1086 // when dispatching change events during selectOption, which
1087 // gets called from RenderMenuList::valueChanged, which gets called
1088 // after the user makes a selection from the menu.
1089 saveLastSelection();
1090 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1091 menuList->showPopup();
1093 } else if (keyCode == '\r') {
1095 form()->submitImplicitly(event, false);
1096 dispatchChangeEventForMenuList();
1100 int listIndex = optionToListIndex(selectedIndex());
1101 if (keyCode == '\r') {
1102 // listIndex should already be selected, but this will fire the onchange handler.
1103 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1108 event->setDefaultHandled();
1111 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
1113 if (renderer() && renderer()->isMenuList()) {
1114 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1115 if (menuList->popupIsVisible())
1116 menuList->hidePopup();
1118 // Save the selection so it can be compared to the new
1119 // selection when we call onChange during selectOption,
1120 // which gets called from RenderMenuList::valueChanged,
1121 // which gets called after the user makes a selection from
1123 saveLastSelection();
1124 menuList->showPopup();
1128 event->setDefaultHandled();
1132 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1134 ASSERT(listIndex >= 0);
1136 // Save the selection so it can be compared to the new selection when
1137 // dispatching change events during mouseup, or after autoscroll finishes.
1138 saveLastSelection();
1140 m_activeSelectionState = true;
1142 bool shiftSelect = m_multiple && shift;
1143 bool multiSelect = m_multiple && multi && !shift;
1145 HTMLElement* clickedElement = listItems()[listIndex];
1146 if (clickedElement->hasTagName(optionTag)) {
1147 // Keep track of whether an active selection (like during drag
1148 // selection), should select or deselect.
1149 if (toHTMLOptionElement(clickedElement)->selected() && multi)
1150 m_activeSelectionState = false;
1151 if (!m_activeSelectionState)
1152 toHTMLOptionElement(clickedElement)->setSelectedState(false);
1155 // If we're not in any special multiple selection mode, then deselect all
1156 // other items, excluding the clicked option. If no option was clicked, then
1157 // this will deselect all items in the list.
1158 if (!shiftSelect && !multiSelect)
1159 deselectItemsWithoutValidation(clickedElement);
1161 // If the anchor hasn't been set, and we're doing a single selection or a
1162 // shift selection, then initialize the anchor to the first selected index.
1163 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1164 setActiveSelectionAnchorIndex(selectedIndex());
1166 // Set the selection state of the clicked option.
1167 if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->disabled())
1168 toHTMLOptionElement(clickedElement)->setSelectedState(true);
1170 // If there was no selectedIndex() for the previous initialization, or If
1171 // we're doing a single selection, or a multiple selection (using cmd or
1172 // ctrl), then initialize the anchor index to the listIndex that just got
1174 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1175 setActiveSelectionAnchorIndex(listIndex);
1177 setActiveSelectionEndIndex(listIndex);
1178 updateListBoxSelection(!multiSelect);
1181 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1183 const Vector<HTMLElement*>& listItems = this->listItems();
1185 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
1187 // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
1191 // Convert to coords relative to the list box if needed.
1192 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
1193 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
1194 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toSize(localOffset));
1195 if (listIndex >= 0) {
1196 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
1197 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
1199 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
1201 if (Frame* frame = document()->frame())
1202 frame->eventHandler()->setMouseDownMayStartAutoscroll();
1204 event->setDefaultHandled();
1206 } else if (event->type() == eventNames().mousemoveEvent && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
1207 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
1208 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
1211 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
1212 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toSize(localOffset));
1213 if (listIndex >= 0) {
1215 setActiveSelectionEndIndex(listIndex);
1216 updateListBoxSelection(false);
1218 updateSelectedState(listIndex, false, false);
1219 event->setDefaultHandled();
1221 } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer()) {
1222 // This makes sure we fire dispatchFormControlChangeEvent for a single
1223 // click. For drag selection, onChange will fire when the autoscroll
1226 } else if (event->type() == eventNames().keydownEvent) {
1227 if (!event->isKeyboardEvent())
1229 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
1231 bool handled = false;
1233 if (m_activeSelectionEndIndex < 0) {
1234 // Initialize the end index
1235 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1236 int startIndex = lastSelectedListIndex();
1238 if (keyIdentifier == "Down")
1239 endIndex = nextSelectableListIndex(startIndex);
1241 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1242 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1243 int startIndex = optionToListIndex(selectedIndex());
1245 if (keyIdentifier == "Up")
1246 endIndex = previousSelectableListIndex(startIndex);
1248 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1251 // Set the end index based on the current end index.
1252 if (keyIdentifier == "Down") {
1253 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1255 } else if (keyIdentifier == "Up") {
1256 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1258 } else if (keyIdentifier == "PageDown") {
1259 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1261 } else if (keyIdentifier == "PageUp") {
1262 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1266 if (keyIdentifier == "Home") {
1267 endIndex = firstSelectableListIndex();
1269 } else if (keyIdentifier == "End") {
1270 endIndex = lastSelectableListIndex();
1274 if (isSpatialNavigationEnabled(document()->frame()))
1275 // Check if the selection moves to the boundary.
1276 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1279 if (endIndex >= 0 && handled) {
1280 // Save the selection so it can be compared to the new selection
1281 // when dispatching change events immediately after making the new
1283 saveLastSelection();
1285 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1286 setActiveSelectionEndIndex(endIndex);
1288 bool selectNewItem = !m_multiple || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(document()->frame());
1290 m_activeSelectionState = true;
1291 // If the anchor is unitialized, or if we're going to deselect all
1292 // other options, then set the anchor index equal to the end index.
1293 bool deselectOthers = !m_multiple || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem);
1294 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1296 deselectItemsWithoutValidation();
1297 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1300 toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
1301 if (selectNewItem) {
1302 updateListBoxSelection(deselectOthers);
1305 scrollToSelection();
1307 event->setDefaultHandled();
1309 } else if (event->type() == eventNames().keypressEvent) {
1310 if (!event->isKeyboardEvent())
1312 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
1314 if (keyCode == '\r') {
1316 form()->submitImplicitly(event, false);
1317 event->setDefaultHandled();
1318 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) {
1319 // Use space to toggle selection change.
1320 m_activeSelectionState = !m_activeSelectionState;
1321 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
1323 event->setDefaultHandled();
1328 void HTMLSelectElement::defaultEventHandler(Event* event)
1334 menuListDefaultEventHandler(event);
1336 listBoxDefaultEventHandler(event);
1337 if (event->defaultHandled())
1340 if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) {
1341 KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event);
1342 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
1343 typeAheadFind(keyboardEvent);
1344 event->setDefaultHandled();
1348 HTMLFormControlElementWithState::defaultEventHandler(event);
1351 int HTMLSelectElement::lastSelectedListIndex() const
1353 const Vector<HTMLElement*>& items = listItems();
1354 for (size_t i = items.size(); i;) {
1355 HTMLElement* element = items[--i];
1356 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
1362 static String stripLeadingWhiteSpace(const String& string)
1364 int length = string.length();
1367 for (i = 0; i < length; ++i) {
1368 if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
1372 return string.substring(i, length - i);
1375 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
1377 if (event->timeStamp() < m_lastCharTime)
1380 DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
1381 m_lastCharTime = event->timeStamp();
1383 UChar c = event->charCode();
1386 int searchStartOffset = 1;
1387 if (delta > typeAheadTimeout) {
1388 prefix = String(&c, 1);
1389 m_typedString = prefix;
1390 m_repeatingChar = c;
1392 m_typedString.append(c);
1394 if (c == m_repeatingChar) {
1395 // The user is likely trying to cycle through all the items starting
1396 // with this character, so just search on the character.
1397 prefix = String(&c, 1);
1399 m_repeatingChar = 0;
1400 prefix = m_typedString;
1401 searchStartOffset = 0;
1405 const Vector<HTMLElement*>& items = listItems();
1406 int itemCount = items.size();
1410 int selected = selectedIndex();
1411 int index = (optionToListIndex(selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
1414 // Compute a case-folded copy of the prefix string before beginning the search for
1415 // a matching element. This code uses foldCase to work around the fact that
1416 // String::startWith does not fold non-ASCII characters. This code can be changed
1417 // to use startWith once that is fixed.
1418 String prefixWithCaseFolded(prefix.foldCase());
1419 for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) {
1420 HTMLElement* element = items[index];
1421 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->disabled())
1424 // Fold the option string and check if its prefix is equal to the folded prefix.
1425 String text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
1426 if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) {
1427 selectOption(listToOptionIndex(index), DeselectOtherOptions | UserDriven);
1428 if (!usesMenuList())
1431 setOptionsChangedOnRenderer();
1432 setNeedsStyleRecalc();
1438 void HTMLSelectElement::insertedIntoTree(bool deep)
1440 // When the element is created during document parsing, it won't have any
1441 // items yet - but for innerHTML and related methods, this method is called
1442 // after the whole subtree is constructed.
1444 HTMLFormControlElementWithState::insertedIntoTree(deep);
1447 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1449 // First bring into focus the list box.
1451 accessKeyAction(false);
1453 // If this index is already selected, unselect. otherwise update the selected index.
1454 const Vector<HTMLElement*>& items = listItems();
1455 int listIndex = optionToListIndex(index);
1456 if (listIndex >= 0) {
1457 HTMLElement* element = items[listIndex];
1458 if (element->hasTagName(optionTag)) {
1459 if (toHTMLOptionElement(element)->selected())
1460 toHTMLOptionElement(element)->setSelectedState(false);
1462 selectOption(index, DispatchChangeEvent | UserDriven);
1467 dispatchChangeEventForMenuList();
1471 scrollToSelection();
1474 unsigned HTMLSelectElement::length() const
1476 unsigned options = 0;
1478 const Vector<HTMLElement*>& items = listItems();
1479 for (unsigned i = 0; i < items.size(); ++i) {
1480 if (items[i]->hasTagName(optionTag))
1489 HTMLSelectElement* toHTMLSelectElement(Node* node)
1491 ASSERT(!node || node->hasTagName(selectTag));
1492 return static_cast<HTMLSelectElement*>(node);
1495 const HTMLSelectElement* toHTMLSelectElement(const Node* node)
1497 ASSERT(!node || node->hasTagName(selectTag));
1498 return static_cast<const HTMLSelectElement*>(node);