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 "FormController.h"
37 #include "FormDataList.h"
39 #include "HTMLFormElement.h"
40 #include "HTMLNames.h"
41 #include "HTMLOptGroupElement.h"
42 #include "HTMLOptionElement.h"
43 #include "HTMLOptionsCollection.h"
44 #include "KeyboardEvent.h"
45 #include "LocalizedStrings.h"
46 #include "MouseEvent.h"
47 #include "NodeRenderingContext.h"
49 #include "RenderListBox.h"
50 #include "RenderMenuList.h"
51 #include "RenderTheme.h"
52 #include "ScriptEventListener.h"
53 #include "SpatialNavigation.h"
54 #include <wtf/text/StringBuilder.h>
55 #include <wtf/unicode/Unicode.h>
58 using namespace WTF::Unicode;
62 using namespace HTMLNames;
64 // Upper limit agreed upon with representatives of Opera and Mozilla.
65 static const unsigned maxSelectItems = 10000;
67 static const DOMTimeStamp typeAheadTimeout = 1000;
69 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
70 : HTMLFormControlElementWithState(tagName, document, form)
73 , m_lastOnChangeIndex(-1)
74 , m_activeSelectionAnchorIndex(-1)
75 , m_activeSelectionEndIndex(-1)
77 , m_isProcessingUserDrivenChange(false)
79 , m_activeSelectionState(false)
80 , m_shouldRecalcListItems(false)
82 ASSERT(hasTagName(selectTag));
85 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
87 ASSERT(tagName.matches(selectTag));
88 return adoptRef(new HTMLSelectElement(tagName, document, form));
91 const AtomicString& HTMLSelectElement::formControlType() const
93 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple"));
94 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one"));
95 return m_multiple ? selectMultiple : selectOne;
98 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
100 deselectItemsWithoutValidation(excludeElement);
101 setNeedsValidityCheck();
104 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
106 // User interaction such as mousedown events can cause list box select elements to send change events.
107 // This produces that same behavior for changes triggered by other code running on behalf of the user.
108 if (!usesMenuList()) {
109 updateSelectedState(optionIndex, allowMultipleSelection, false);
110 setNeedsValidityCheck();
116 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
117 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
118 // The selectOption function does not behave this way, possibly because other callers need a change event even
119 // in cases where the selected option is not change.
120 if (optionIndex == selectedIndex())
123 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
126 bool HTMLSelectElement::hasPlaceholderLabelOption() const
128 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
130 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
131 // Using "size() > 1" here because size() may be 0 in WebKit.
132 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
134 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
135 // In this case, the display size should be assumed as the default.
136 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
138 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
139 if (multiple() || size() > 1)
142 int listIndex = optionToListIndex(0);
143 ASSERT(listIndex >= 0);
146 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]);
147 return !listIndex && option->value().isEmpty();
150 String HTMLSelectElement::validationMessage() const
156 return customValidationMessage();
158 return valueMissing() ? validationMessageValueMissingForSelectText() : String();
161 bool HTMLSelectElement::valueMissing() const
166 if (!isRequiredFormControl())
169 int firstSelectionIndex = selectedIndex();
171 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
172 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
175 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
178 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
180 updateSelectedState(listIndex, allowMultiplySelections, shift);
181 setNeedsValidityCheck();
187 bool HTMLSelectElement::usesMenuList() const
189 const Page* page = document()->page();
190 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme();
191 if (renderTheme->delegatesMenuListRendering())
194 return !m_multiple && m_size <= 1;
197 int HTMLSelectElement::activeSelectionStartListIndex() const
199 if (m_activeSelectionAnchorIndex >= 0)
200 return m_activeSelectionAnchorIndex;
201 return optionToListIndex(selectedIndex());
204 int HTMLSelectElement::activeSelectionEndListIndex() const
206 if (m_activeSelectionEndIndex >= 0)
207 return m_activeSelectionEndIndex;
208 return lastSelectedListIndex();
211 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec)
213 // Make sure the element is ref'd and deref'd so we don't leak it.
214 RefPtr<HTMLElement> protectNewChild(element);
216 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
219 insertBefore(element, before, ec);
220 setNeedsValidityCheck();
223 void HTMLSelectElement::remove(int optionIndex)
225 int listIndex = optionToListIndex(optionIndex);
230 listItems()[listIndex]->remove(ec);
233 void HTMLSelectElement::remove(HTMLOptionElement* option)
235 if (option->ownerSelectElement() != this)
242 String HTMLSelectElement::value() const
244 const Vector<HTMLElement*>& items = listItems();
245 for (unsigned i = 0; i < items.size(); i++) {
246 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
247 return static_cast<HTMLOptionElement*>(items[i])->value();
252 void HTMLSelectElement::setValue(const String &value)
254 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
255 if (value.isNull()) {
256 setSelectedIndex(-1);
260 // Find the option with value() matching the given parameter and make it the current selection.
261 const Vector<HTMLElement*>& items = listItems();
262 unsigned optionIndex = 0;
263 for (unsigned i = 0; i < items.size(); i++) {
264 if (items[i]->hasLocalName(optionTag)) {
265 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
266 setSelectedIndex(optionIndex);
273 setSelectedIndex(-1);
276 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
278 if (name == alignAttr) {
279 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
280 // See http://bugs.webkit.org/show_bug.cgi?id=12072
284 return HTMLFormControlElementWithState::isPresentationAttribute(name);
287 void HTMLSelectElement::parseAttribute(const Attribute& attribute)
289 if (attribute.name() == sizeAttr) {
290 int oldSize = m_size;
291 // Set the attribute value to a number.
292 // This is important since the style rules for this attribute can determine the appearance property.
293 int size = attribute.value().toInt();
294 String attrSize = String::number(size);
295 if (attrSize != attribute.value()) {
296 // FIXME: This is horribly factored.
297 if (Attribute* sizeAttribute = getAttributeItem(sizeAttr))
298 sizeAttribute->setValue(attrSize);
302 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
304 updateListItemSelectedStates();
307 setNeedsValidityCheck();
308 if (m_size != oldSize && attached()) {
310 setRecalcListItems();
312 } else if (attribute.name() == multipleAttr)
313 parseMultipleAttribute(attribute);
314 else if (attribute.name() == accesskeyAttr) {
315 // FIXME: ignore for the moment.
316 } else if (attribute.name() == onchangeAttr)
317 setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attribute));
319 HTMLFormControlElementWithState::parseAttribute(attribute);
322 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
325 return isFocusable();
326 return HTMLFormControlElementWithState::isKeyboardFocusable(event);
329 bool HTMLSelectElement::isMouseFocusable() const
332 return isFocusable();
333 return HTMLFormControlElementWithState::isMouseFocusable();
336 bool HTMLSelectElement::canSelectAll() const
338 return !usesMenuList();
341 RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
344 return new (arena) RenderMenuList(this);
345 return new (arena) RenderListBox(this);
348 bool HTMLSelectElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const
350 return childContext.isOnUpperEncapsulationBoundary() && HTMLFormControlElementWithState::childShouldCreateRenderer(childContext);
353 HTMLCollection* HTMLSelectElement::selectedOptions()
355 if (!m_selectedOptionsCollection)
356 m_selectedOptionsCollection = HTMLCollection::create(this, SelectedOptions);
357 return m_selectedOptionsCollection.get();
360 HTMLOptionsCollection* HTMLSelectElement::options()
362 if (!m_optionsCollection)
363 m_optionsCollection = HTMLOptionsCollection::create(this);
364 return m_optionsCollection.get();
367 void HTMLSelectElement::updateListItemSelectedStates()
369 if (m_shouldRecalcListItems)
373 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
375 setRecalcListItems();
376 setNeedsValidityCheck();
378 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
380 if (AXObjectCache::accessibilityEnabled() && renderer())
381 renderer()->document()->axObjectCache()->childrenChanged(renderer());
384 void HTMLSelectElement::optionElementChildrenChanged()
386 setRecalcListItems();
387 setNeedsValidityCheck();
389 if (AXObjectCache::accessibilityEnabled() && renderer())
390 renderer()->document()->axObjectCache()->childrenChanged(renderer());
393 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
396 dispatchSimulatedClick(0, sendMouseEvents);
399 void HTMLSelectElement::setMultiple(bool multiple)
401 bool oldMultiple = this->multiple();
402 int oldSelectedIndex = selectedIndex();
403 setAttribute(multipleAttr, multiple ? "" : 0);
405 // Restore selectedIndex after changing the multiple flag to preserve
406 // selection as single-line and multi-line has different defaults.
407 if (oldMultiple != this->multiple())
408 setSelectedIndex(oldSelectedIndex);
411 void HTMLSelectElement::setSize(int size)
413 setAttribute(sizeAttr, String::number(size));
416 Node* HTMLSelectElement::namedItem(const AtomicString& name)
418 return options()->namedItem(name);
421 Node* HTMLSelectElement::item(unsigned index)
423 return options()->item(index);
426 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
429 if (index > maxSelectItems - 1)
430 index = maxSelectItems - 1;
431 int diff = index - length();
432 RefPtr<HTMLElement> before = 0;
433 // Out of array bounds? First insert empty dummies.
435 setLength(index, ec);
436 // Replace an existing entry?
437 } else if (diff < 0) {
438 before = toHTMLElement(options()->item(index+1));
441 // Finally add the new element.
443 add(option, before.get(), ec);
444 if (diff >= 0 && option->selected())
445 optionSelectionStateChanged(option, true);
449 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
452 if (newLen > maxSelectItems)
453 newLen = maxSelectItems;
454 int diff = length() - newLen;
456 if (diff < 0) { // Add dummy elements.
458 RefPtr<Element> option = document()->createElement(optionTag, false);
460 add(toHTMLElement(option.get()), 0, ec);
465 const Vector<HTMLElement*>& items = listItems();
467 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
468 // of elements that we intend to remove then attempt to remove them one at a time.
469 Vector<RefPtr<Element> > itemsToRemove;
470 size_t optionIndex = 0;
471 for (size_t i = 0; i < items.size(); ++i) {
472 Element* item = items[i];
473 if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
474 ASSERT(item->parentNode());
475 itemsToRemove.append(item);
479 for (size_t i = 0; i < itemsToRemove.size(); ++i) {
480 Element* item = itemsToRemove[i].get();
481 if (item->parentNode())
482 item->parentNode()->removeChild(item, ec);
485 setNeedsValidityCheck();
488 bool HTMLSelectElement::isRequiredFormControl() const
493 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
494 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
495 // Otherwise, it returns |listIndex|.
496 // Valid means that it is enabled and an option element.
497 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
499 ASSERT(direction == -1 || direction == 1);
500 const Vector<HTMLElement*>& listItems = this->listItems();
501 int lastGoodIndex = listIndex;
502 int size = listItems.size();
503 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
505 if (!listItems[listIndex]->disabled() && listItems[listIndex]->hasTagName(optionTag)) {
506 lastGoodIndex = listIndex;
511 return lastGoodIndex;
514 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
516 return nextValidIndex(startIndex, SkipForwards, 1);
519 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
521 if (startIndex == -1)
522 startIndex = listItems().size();
523 return nextValidIndex(startIndex, SkipBackwards, 1);
526 int HTMLSelectElement::firstSelectableListIndex() const
528 const Vector<HTMLElement*>& items = listItems();
529 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
530 if (static_cast<size_t>(index) == items.size())
535 int HTMLSelectElement::lastSelectableListIndex() const
537 return nextValidIndex(-1, SkipForwards, INT_MAX);
540 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
541 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
543 const Vector<HTMLElement*>& items = listItems();
544 // Can't use m_size because renderer forces a minimum size.
546 if (renderer()->isListBox())
547 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
549 // One page away, but not outside valid bounds.
550 // If there is a valid option item one page away, the index is chosen.
551 // If there is no exact one page away valid option, returns startIndex or the most far index.
552 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
553 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
554 return nextValidIndex(edgeIndex, direction, skipAmount);
557 void HTMLSelectElement::selectAll()
559 ASSERT(!usesMenuList());
560 if (!renderer() || !m_multiple)
563 // Save the selection so it can be compared to the new selectAll selection
564 // when dispatching change events.
567 m_activeSelectionState = true;
568 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
569 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
571 updateListBoxSelection(false);
573 setNeedsValidityCheck();
576 void HTMLSelectElement::saveLastSelection()
578 if (usesMenuList()) {
579 m_lastOnChangeIndex = selectedIndex();
583 m_lastOnChangeSelection.clear();
584 const Vector<HTMLElement*>& items = listItems();
585 for (unsigned i = 0; i < items.size(); ++i) {
586 HTMLElement* element = items[i];
587 m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
591 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
593 m_activeSelectionAnchorIndex = index;
595 // Cache the selection state so we can restore the old selection as the new
596 // selection pivots around this anchor index.
597 m_cachedStateForActiveSelection.clear();
599 const Vector<HTMLElement*>& items = listItems();
600 for (unsigned i = 0; i < items.size(); ++i) {
601 HTMLElement* element = items[i];
602 m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
606 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
608 m_activeSelectionEndIndex = index;
611 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
613 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
614 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
616 unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
617 unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
619 const Vector<HTMLElement*>& items = listItems();
620 for (unsigned i = 0; i < items.size(); ++i) {
621 HTMLElement* element = items[i];
622 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->disabled())
625 if (i >= start && i <= end)
626 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
627 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
628 toHTMLOptionElement(element)->setSelectedState(false);
630 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
634 setNeedsValidityCheck();
637 void HTMLSelectElement::listBoxOnChange()
639 ASSERT(!usesMenuList() || m_multiple);
641 const Vector<HTMLElement*>& items = listItems();
643 // If the cached selection list is empty, or the size has changed, then fire
644 // dispatchFormControlChangeEvent, and return early.
645 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
646 dispatchFormControlChangeEvent();
650 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
651 bool fireOnChange = false;
652 for (unsigned i = 0; i < items.size(); ++i) {
653 HTMLElement* element = items[i];
654 bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
655 if (selected != m_lastOnChangeSelection[i])
657 m_lastOnChangeSelection[i] = selected;
661 dispatchFormControlChangeEvent();
664 void HTMLSelectElement::dispatchChangeEventForMenuList()
666 ASSERT(usesMenuList());
668 int selected = selectedIndex();
669 if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
670 m_lastOnChangeIndex = selected;
671 m_isProcessingUserDrivenChange = false;
672 dispatchFormControlChangeEvent();
676 void HTMLSelectElement::scrollToSelection()
681 if (RenderObject* renderer = this->renderer())
682 toRenderListBox(renderer)->selectionChanged();
685 void HTMLSelectElement::setOptionsChangedOnRenderer()
687 if (RenderObject* renderer = this->renderer()) {
689 toRenderMenuList(renderer)->setOptionsChanged(true);
691 toRenderListBox(renderer)->setOptionsChanged(true);
695 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
697 if (m_shouldRecalcListItems)
701 Vector<HTMLElement*> items = m_listItems;
702 recalcListItems(false);
703 ASSERT(items == m_listItems);
710 void HTMLSelectElement::invalidateSelectedItems()
712 if (m_selectedOptionsCollection)
713 m_selectedOptionsCollection->invalidateCache();
716 void HTMLSelectElement::setRecalcListItems()
718 m_shouldRecalcListItems = true;
719 // Manual selection anchor is reset when manipulating the select programmatically.
720 m_activeSelectionAnchorIndex = -1;
721 setOptionsChangedOnRenderer();
722 setNeedsStyleRecalc();
723 if (!inDocument() && m_optionsCollection)
724 m_optionsCollection->invalidateCacheIfNeeded();
725 if (!inDocument() && m_selectedOptionsCollection)
726 m_selectedOptionsCollection->invalidateCacheIfNeeded();
729 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
733 m_shouldRecalcListItems = false;
735 HTMLOptionElement* foundSelected = 0;
736 HTMLOptionElement* firstOption = 0;
737 for (Node* currentNode = this->firstChild(); currentNode;) {
738 if (!currentNode->isHTMLElement()) {
739 currentNode = currentNode->traverseNextSibling(this);
743 HTMLElement* current = toHTMLElement(currentNode);
745 // optgroup tags may not nest. However, both FireFox and IE will
746 // flatten the tree automatically, so we follow suit.
747 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
748 if (current->hasTagName(optgroupTag)) {
749 m_listItems.append(current);
750 if (current->firstChild()) {
751 currentNode = current->firstChild();
756 if (current->hasTagName(optionTag)) {
757 m_listItems.append(current);
759 if (updateSelectedStates && !m_multiple) {
760 HTMLOptionElement* option = toHTMLOptionElement(current);
762 firstOption = option;
763 if (option->selected()) {
765 foundSelected->setSelectedState(false);
766 foundSelected = option;
767 } else if (m_size <= 1 && !foundSelected && !option->disabled()) {
768 foundSelected = option;
769 foundSelected->setSelectedState(true);
774 if (current->hasTagName(hrTag))
775 m_listItems.append(current);
777 // In conforming HTML code, only <optgroup> and <option> will be found
778 // within a <select>. We call traverseNextSibling so that we only step
779 // into those tags that we choose to. For web-compat, we should cope
780 // with the case where odd tags like a <div> have been added but we
781 // handle this because such tags have already been removed from the
782 // <select>'s subtree at this point.
783 currentNode = currentNode->traverseNextSibling(this);
786 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
787 firstOption->setSelectedState(true);
790 int HTMLSelectElement::selectedIndex() const
794 // Return the number of the first option selected.
795 const Vector<HTMLElement*>& items = listItems();
796 for (size_t i = 0; i < items.size(); ++i) {
797 HTMLElement* element = items[i];
798 if (element->hasTagName(optionTag)) {
799 if (toHTMLOptionElement(element)->selected())
808 void HTMLSelectElement::setSelectedIndex(int index)
810 selectOption(index, DeselectOtherOptions);
813 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
815 ASSERT(option->ownerSelectElement() == this);
816 if (optionIsSelected)
817 selectOption(option->index());
818 else if (!usesMenuList())
821 selectOption(nextSelectableListIndex(-1));
824 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
826 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
828 const Vector<HTMLElement*>& items = listItems();
829 int listIndex = optionToListIndex(optionIndex);
831 HTMLElement* element = 0;
832 if (listIndex >= 0) {
833 element = items[listIndex];
834 if (element->hasTagName(optionTag)) {
835 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
836 setActiveSelectionAnchorIndex(listIndex);
837 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
838 setActiveSelectionEndIndex(listIndex);
839 toHTMLOptionElement(element)->setSelectedState(true);
844 deselectItemsWithoutValidation(element);
846 // For the menu list case, this is what makes the selected element appear.
847 if (RenderObject* renderer = this->renderer())
848 renderer->updateFromElement();
852 if (usesMenuList()) {
853 m_isProcessingUserDrivenChange = flags & UserDriven;
854 if (flags & DispatchChangeEvent)
855 dispatchChangeEventForMenuList();
856 if (RenderObject* renderer = this->renderer()) {
858 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
859 else if (renderer->isListBox())
860 toRenderListBox(renderer)->selectionChanged();
864 setNeedsValidityCheck();
865 if (Frame* frame = document()->frame())
866 frame->page()->chrome()->client()->formStateDidChange(this);
869 int HTMLSelectElement::optionToListIndex(int optionIndex) const
871 const Vector<HTMLElement*>& items = listItems();
872 int listSize = static_cast<int>(items.size());
873 if (optionIndex < 0 || optionIndex >= listSize)
876 int optionIndex2 = -1;
877 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
878 if (items[listIndex]->hasTagName(optionTag)) {
880 if (optionIndex2 == optionIndex)
888 int HTMLSelectElement::listToOptionIndex(int listIndex) const
890 const Vector<HTMLElement*>& items = listItems();
891 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag))
894 // Actual index of option not counting OPTGROUP entries that may be in list.
896 for (int i = 0; i < listIndex; ++i) {
897 if (items[i]->hasTagName(optionTag))
904 void HTMLSelectElement::dispatchFocusEvent(PassRefPtr<Node> oldFocusedNode)
906 // Save the selection so it can be compared to the new selection when
907 // dispatching change events during blur event dispatch.
910 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedNode);
913 void HTMLSelectElement::dispatchBlurEvent(PassRefPtr<Node> newFocusedNode)
915 // We only need to fire change events here for menu lists, because we fire
916 // change events for list boxes whenever the selection change is actually made.
917 // This matches other browsers' behavior.
919 dispatchChangeEventForMenuList();
920 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedNode);
923 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
925 const Vector<HTMLElement*>& items = listItems();
926 for (unsigned i = 0; i < items.size(); ++i) {
927 HTMLElement* element = items[i];
928 if (element != excludeElement && element->hasTagName(optionTag))
929 toHTMLOptionElement(element)->setSelectedState(false);
933 FormControlState HTMLSelectElement::saveFormControlState() const
935 const Vector<HTMLElement*>& items = listItems();
936 size_t length = items.size();
937 FormControlState state;
938 for (unsigned i = 0; i < length; ++i) {
939 if (!items[i]->hasTagName(optionTag))
941 HTMLOptionElement* option = toHTMLOptionElement(items[i]);
942 if (!option->selected())
944 state.append(option->value());
951 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
953 const Vector<HTMLElement*>& items = listItems();
954 size_t loopEndIndex = std::min(items.size(), listIndexEnd);
955 for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
956 if (!items[i]->hasLocalName(optionTag))
958 if (static_cast<HTMLOptionElement*>(items[i])->value() == value)
964 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
968 const Vector<HTMLElement*>& items = listItems();
969 size_t itemsSize = items.size();
973 for (size_t i = 0; i < itemsSize; ++i) {
974 if (!items[i]->hasLocalName(optionTag))
976 static_cast<HTMLOptionElement*>(items[i])->setSelectedState(false);
980 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
981 if (foundIndex != notFound)
982 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
984 size_t startIndex = 0;
985 for (size_t i = 0; i < state.valueSize(); ++i) {
986 const String& value = state[i];
987 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
988 if (foundIndex == notFound)
989 foundIndex = searchOptionsForValue(value, 0, startIndex);
990 if (foundIndex == notFound)
992 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
993 startIndex = foundIndex + 1;
997 setOptionsChangedOnRenderer();
998 setNeedsValidityCheck();
1001 void HTMLSelectElement::parseMultipleAttribute(const Attribute& attribute)
1003 bool oldUsesMenuList = usesMenuList();
1004 m_multiple = !attribute.isNull();
1005 setNeedsValidityCheck();
1006 if (oldUsesMenuList != usesMenuList())
1007 reattachIfAttached();
1010 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
1012 const AtomicString& name = this->name();
1016 bool successful = false;
1017 const Vector<HTMLElement*>& items = listItems();
1019 for (unsigned i = 0; i < items.size(); ++i) {
1020 HTMLElement* element = items[i];
1021 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->disabled()) {
1022 list.appendData(name, toHTMLOptionElement(element)->value());
1027 // It's possible that this is a menulist with multiple options and nothing
1028 // will be submitted (!successful). We won't send a unselected non-disabled
1029 // option as fallback. This behavior matches to other browsers.
1033 void HTMLSelectElement::reset()
1035 HTMLOptionElement* firstOption = 0;
1036 HTMLOptionElement* selectedOption = 0;
1038 const Vector<HTMLElement*>& items = listItems();
1039 for (unsigned i = 0; i < items.size(); ++i) {
1040 HTMLElement* element = items[i];
1041 if (!element->hasTagName(optionTag))
1044 if (items[i]->fastHasAttribute(selectedAttr)) {
1045 if (selectedOption && !m_multiple)
1046 selectedOption->setSelectedState(false);
1047 toHTMLOptionElement(element)->setSelectedState(true);
1048 selectedOption = toHTMLOptionElement(element);
1050 toHTMLOptionElement(element)->setSelectedState(false);
1053 firstOption = toHTMLOptionElement(element);
1056 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
1057 firstOption->setSelectedState(true);
1059 setOptionsChangedOnRenderer();
1060 setNeedsStyleRecalc();
1061 setNeedsValidityCheck();
1064 #if !PLATFORM(WIN) || OS(WINCE)
1065 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
1067 const Page* page = document()->page();
1068 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme();
1070 if (!renderTheme->popsMenuByArrowKeys())
1073 if (!isSpatialNavigationEnabled(document()->frame())) {
1074 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
1076 // Calling focus() may cause us to lose our renderer. Return true so
1077 // that our caller doesn't process the event further, but don't set
1078 // the event as handled.
1082 // Save the selection so it can be compared to the new selection
1083 // when dispatching change events during selectOption, which
1084 // gets called from RenderMenuList::valueChanged, which gets called
1085 // after the user makes a selection from the menu.
1086 saveLastSelection();
1087 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1088 menuList->showPopup();
1089 event->setDefaultHandled();
1098 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1100 const Page* page = document()->page();
1101 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme();
1103 if (event->type() == eventNames().keydownEvent) {
1104 if (!renderer() || !event->isKeyboardEvent())
1107 if (platformHandleKeydownEvent(static_cast<KeyboardEvent*>(event)))
1110 // When using spatial navigation, we want to be able to navigate away
1111 // from the select element when the user hits any of the arrow keys,
1112 // instead of changing the selection.
1113 if (isSpatialNavigationEnabled(document()->frame())) {
1114 if (!m_activeSelectionState)
1118 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
1119 bool handled = true;
1120 const Vector<HTMLElement*>& listItems = this->listItems();
1121 int listIndex = optionToListIndex(selectedIndex());
1123 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1124 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1125 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1126 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1127 else if (keyIdentifier == "PageDown")
1128 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1129 else if (keyIdentifier == "PageUp")
1130 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1131 else if (keyIdentifier == "Home")
1132 listIndex = nextValidIndex(-1, SkipForwards, 1);
1133 else if (keyIdentifier == "End")
1134 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1138 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1139 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1142 event->setDefaultHandled();
1145 // Use key press event here since sending simulated mouse events
1146 // on key down blocks the proper sending of the key press event.
1147 if (event->type() == eventNames().keypressEvent) {
1148 if (!renderer() || !event->isKeyboardEvent())
1151 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
1152 bool handled = false;
1154 if (keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) {
1155 // Use space to toggle arrow key handling for selection change or spatial navigation.
1156 m_activeSelectionState = !m_activeSelectionState;
1157 event->setDefaultHandled();
1161 if (renderTheme->popsMenuBySpaceOrReturn()) {
1162 if (keyCode == ' ' || keyCode == '\r') {
1165 // Calling focus() may cause us to lose our renderer, in which case
1166 // do not want to handle the event.
1170 // Save the selection so it can be compared to the new selection
1171 // when dispatching change events during selectOption, which
1172 // gets called from RenderMenuList::valueChanged, which gets called
1173 // after the user makes a selection from the menu.
1174 saveLastSelection();
1175 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1176 menuList->showPopup();
1179 } else if (renderTheme->popsMenuByArrowKeys()) {
1180 if (keyCode == ' ') {
1183 // Calling focus() may cause us to lose our renderer, in which case
1184 // do not want to handle the event.
1188 // Save the selection so it can be compared to the new selection
1189 // when dispatching change events during selectOption, which
1190 // gets called from RenderMenuList::valueChanged, which gets called
1191 // after the user makes a selection from the menu.
1192 saveLastSelection();
1193 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1194 menuList->showPopup();
1196 } else if (keyCode == '\r') {
1198 form()->submitImplicitly(event, false);
1199 dispatchChangeEventForMenuList();
1205 event->setDefaultHandled();
1208 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
1210 if (renderer() && renderer()->isMenuList()) {
1211 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1212 if (menuList->popupIsVisible())
1213 menuList->hidePopup();
1215 // Save the selection so it can be compared to the new
1216 // selection when we call onChange during selectOption,
1217 // which gets called from RenderMenuList::valueChanged,
1218 // which gets called after the user makes a selection from
1220 saveLastSelection();
1221 menuList->showPopup();
1225 event->setDefaultHandled();
1229 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1231 ASSERT(listIndex >= 0);
1233 // Save the selection so it can be compared to the new selection when
1234 // dispatching change events during mouseup, or after autoscroll finishes.
1235 saveLastSelection();
1237 m_activeSelectionState = true;
1239 bool shiftSelect = m_multiple && shift;
1240 bool multiSelect = m_multiple && multi && !shift;
1242 HTMLElement* clickedElement = listItems()[listIndex];
1243 if (clickedElement->hasTagName(optionTag)) {
1244 // Keep track of whether an active selection (like during drag
1245 // selection), should select or deselect.
1246 if (toHTMLOptionElement(clickedElement)->selected() && multiSelect)
1247 m_activeSelectionState = false;
1248 if (!m_activeSelectionState)
1249 toHTMLOptionElement(clickedElement)->setSelectedState(false);
1252 // If we're not in any special multiple selection mode, then deselect all
1253 // other items, excluding the clicked option. If no option was clicked, then
1254 // this will deselect all items in the list.
1255 if (!shiftSelect && !multiSelect)
1256 deselectItemsWithoutValidation(clickedElement);
1258 // If the anchor hasn't been set, and we're doing a single selection or a
1259 // shift selection, then initialize the anchor to the first selected index.
1260 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1261 setActiveSelectionAnchorIndex(selectedIndex());
1263 // Set the selection state of the clicked option.
1264 if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->disabled())
1265 toHTMLOptionElement(clickedElement)->setSelectedState(true);
1267 // If there was no selectedIndex() for the previous initialization, or If
1268 // we're doing a single selection, or a multiple selection (using cmd or
1269 // ctrl), then initialize the anchor index to the listIndex that just got
1271 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1272 setActiveSelectionAnchorIndex(listIndex);
1274 setActiveSelectionEndIndex(listIndex);
1275 updateListBoxSelection(!multiSelect);
1278 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1280 const Vector<HTMLElement*>& listItems = this->listItems();
1282 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
1284 // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
1288 // Convert to coords relative to the list box if needed.
1289 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
1290 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
1291 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toSize(localOffset));
1292 if (listIndex >= 0) {
1293 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
1294 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
1296 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
1298 if (Frame* frame = document()->frame())
1299 frame->eventHandler()->setMouseDownMayStartAutoscroll();
1301 event->setDefaultHandled();
1303 } else if (event->type() == eventNames().mousemoveEvent && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
1304 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
1305 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
1308 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
1309 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toSize(localOffset));
1310 if (listIndex >= 0) {
1312 setActiveSelectionEndIndex(listIndex);
1313 updateListBoxSelection(false);
1315 setActiveSelectionAnchorIndex(listIndex);
1316 setActiveSelectionEndIndex(listIndex);
1317 updateListBoxSelection(true);
1319 event->setDefaultHandled();
1321 } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer()) {
1322 // This makes sure we fire dispatchFormControlChangeEvent for a single
1323 // click. For drag selection, onChange will fire when the autoscroll
1326 } else if (event->type() == eventNames().keydownEvent) {
1327 if (!event->isKeyboardEvent())
1329 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
1331 bool handled = false;
1333 if (m_activeSelectionEndIndex < 0) {
1334 // Initialize the end index
1335 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1336 int startIndex = lastSelectedListIndex();
1338 if (keyIdentifier == "Down")
1339 endIndex = nextSelectableListIndex(startIndex);
1341 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1342 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1343 int startIndex = optionToListIndex(selectedIndex());
1345 if (keyIdentifier == "Up")
1346 endIndex = previousSelectableListIndex(startIndex);
1348 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1351 // Set the end index based on the current end index.
1352 if (keyIdentifier == "Down") {
1353 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1355 } else if (keyIdentifier == "Up") {
1356 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1358 } else if (keyIdentifier == "PageDown") {
1359 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1361 } else if (keyIdentifier == "PageUp") {
1362 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1366 if (keyIdentifier == "Home") {
1367 endIndex = firstSelectableListIndex();
1369 } else if (keyIdentifier == "End") {
1370 endIndex = lastSelectableListIndex();
1374 if (isSpatialNavigationEnabled(document()->frame()))
1375 // Check if the selection moves to the boundary.
1376 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1379 if (endIndex >= 0 && handled) {
1380 // Save the selection so it can be compared to the new selection
1381 // when dispatching change events immediately after making the new
1383 saveLastSelection();
1385 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1386 setActiveSelectionEndIndex(endIndex);
1388 bool selectNewItem = !m_multiple || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(document()->frame());
1390 m_activeSelectionState = true;
1391 // If the anchor is unitialized, or if we're going to deselect all
1392 // other options, then set the anchor index equal to the end index.
1393 bool deselectOthers = !m_multiple || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem);
1394 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1396 deselectItemsWithoutValidation();
1397 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1400 toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
1401 if (selectNewItem) {
1402 updateListBoxSelection(deselectOthers);
1405 scrollToSelection();
1407 event->setDefaultHandled();
1409 } else if (event->type() == eventNames().keypressEvent) {
1410 if (!event->isKeyboardEvent())
1412 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
1414 if (keyCode == '\r') {
1416 form()->submitImplicitly(event, false);
1417 event->setDefaultHandled();
1418 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) {
1419 // Use space to toggle selection change.
1420 m_activeSelectionState = !m_activeSelectionState;
1421 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
1423 event->setDefaultHandled();
1428 void HTMLSelectElement::defaultEventHandler(Event* event)
1434 menuListDefaultEventHandler(event);
1436 listBoxDefaultEventHandler(event);
1437 if (event->defaultHandled())
1440 if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) {
1441 KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event);
1442 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
1443 typeAheadFind(keyboardEvent);
1444 event->setDefaultHandled();
1448 HTMLFormControlElementWithState::defaultEventHandler(event);
1451 int HTMLSelectElement::lastSelectedListIndex() const
1453 const Vector<HTMLElement*>& items = listItems();
1454 for (size_t i = items.size(); i;) {
1455 HTMLElement* element = items[--i];
1456 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
1462 static String stripLeadingWhiteSpace(const String& string)
1464 int length = string.length();
1467 for (i = 0; i < length; ++i) {
1468 if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
1472 return string.substring(i, length - i);
1475 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
1477 if (event->timeStamp() < m_lastCharTime)
1480 DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
1481 m_lastCharTime = event->timeStamp();
1483 UChar c = event->charCode();
1486 int searchStartOffset = 1;
1487 if (delta > typeAheadTimeout) {
1488 prefix = String(&c, 1);
1489 m_typedString = prefix;
1490 m_repeatingChar = c;
1492 m_typedString.append(c);
1494 if (c == m_repeatingChar) {
1495 // The user is likely trying to cycle through all the items starting
1496 // with this character, so just search on the character.
1497 prefix = String(&c, 1);
1499 m_repeatingChar = 0;
1500 prefix = m_typedString;
1501 searchStartOffset = 0;
1505 const Vector<HTMLElement*>& items = listItems();
1506 int itemCount = items.size();
1510 int selected = selectedIndex();
1511 int index = (optionToListIndex(selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
1514 // Compute a case-folded copy of the prefix string before beginning the search for
1515 // a matching element. This code uses foldCase to work around the fact that
1516 // String::startWith does not fold non-ASCII characters. This code can be changed
1517 // to use startWith once that is fixed.
1518 String prefixWithCaseFolded(prefix.foldCase());
1519 for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) {
1520 HTMLElement* element = items[index];
1521 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->disabled())
1524 // Fold the option string and check if its prefix is equal to the folded prefix.
1525 String text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
1526 if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) {
1527 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
1528 if (!usesMenuList())
1531 setOptionsChangedOnRenderer();
1532 setNeedsStyleRecalc();
1538 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
1540 // When the element is created during document parsing, it won't have any
1541 // items yet - but for innerHTML and related methods, this method is called
1542 // after the whole subtree is constructed.
1544 HTMLFormControlElementWithState::insertedInto(insertionPoint);
1545 return InsertionDone;
1548 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1550 // First bring into focus the list box.
1552 accessKeyAction(false);
1554 // If this index is already selected, unselect. otherwise update the selected index.
1555 const Vector<HTMLElement*>& items = listItems();
1556 int listIndex = optionToListIndex(index);
1557 if (listIndex >= 0) {
1558 HTMLElement* element = items[listIndex];
1559 if (element->hasTagName(optionTag)) {
1560 if (toHTMLOptionElement(element)->selected())
1561 toHTMLOptionElement(element)->setSelectedState(false);
1563 selectOption(index, DispatchChangeEvent | UserDriven);
1568 dispatchChangeEventForMenuList();
1572 scrollToSelection();
1575 unsigned HTMLSelectElement::length() const
1577 unsigned options = 0;
1579 const Vector<HTMLElement*>& items = listItems();
1580 for (unsigned i = 0; i < items.size(); ++i) {
1581 if (items[i]->hasTagName(optionTag))
1590 HTMLSelectElement* toHTMLSelectElement(Node* node)
1592 ASSERT(!node || node->hasTagName(selectTag));
1593 return static_cast<HTMLSelectElement*>(node);
1596 const HTMLSelectElement* toHTMLSelectElement(const Node* node)
1598 ASSERT(!node || node->hasTagName(selectTag));
1599 return static_cast<const HTMLSelectElement*>(node);