Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
      3  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      4  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      5  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      6  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights reserved.
      7  *           (C) 2006 Alexey Proskuryakov (ap (at) 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/)
     10  *
     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.
     15  *
     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.
     20  *
     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.
     25  *
     26  */
     27 
     28 #include "config.h"
     29 #include "core/html/HTMLSelectElement.h"
     30 
     31 #include "HTMLNames.h"
     32 #include "bindings/v8/ExceptionMessages.h"
     33 #include "bindings/v8/ExceptionState.h"
     34 #include "bindings/v8/ExceptionStatePlaceholder.h"
     35 #include "core/accessibility/AXObjectCache.h"
     36 #include "core/dom/Attribute.h"
     37 #include "core/dom/ElementTraversal.h"
     38 #include "core/dom/NodeTraversal.h"
     39 #include "core/events/KeyboardEvent.h"
     40 #include "core/events/MouseEvent.h"
     41 #include "core/events/ThreadLocalEventNames.h"
     42 #include "core/html/FormDataList.h"
     43 #include "core/html/HTMLFormElement.h"
     44 #include "core/html/HTMLOptGroupElement.h"
     45 #include "core/html/HTMLOptionElement.h"
     46 #include "core/html/forms/FormController.h"
     47 #include "core/page/EventHandler.h"
     48 #include "core/frame/Frame.h"
     49 #include "core/page/SpatialNavigation.h"
     50 #include "core/rendering/RenderListBox.h"
     51 #include "core/rendering/RenderMenuList.h"
     52 #include "core/rendering/RenderTheme.h"
     53 #include "platform/PlatformMouseEvent.h"
     54 #include "platform/text/PlatformLocale.h"
     55 
     56 using namespace std;
     57 using namespace WTF::Unicode;
     58 
     59 namespace WebCore {
     60 
     61 using namespace HTMLNames;
     62 
     63 // Upper limit agreed upon with representatives of Opera and Mozilla.
     64 static const unsigned maxSelectItems = 10000;
     65 
     66 HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form, bool createdByParser)
     67     : HTMLFormControlElementWithState(selectTag, document, form)
     68     , m_typeAhead(this)
     69     , m_size(0)
     70     , m_lastOnChangeIndex(-1)
     71     , m_activeSelectionAnchorIndex(-1)
     72     , m_activeSelectionEndIndex(-1)
     73     , m_isProcessingUserDrivenChange(false)
     74     , m_multiple(false)
     75     , m_activeSelectionState(false)
     76     , m_shouldRecalcListItems(false)
     77     , m_isParsingInProgress(createdByParser)
     78 {
     79     ScriptWrappable::init(this);
     80 }
     81 
     82 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
     83 {
     84     return adoptRef(new HTMLSelectElement(document, 0, false));
     85 }
     86 
     87 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
     88 {
     89     return adoptRef(new HTMLSelectElement(document, form, createdByParser));
     90 }
     91 
     92 const AtomicString& HTMLSelectElement::formControlType() const
     93 {
     94     DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
     95     DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
     96     return m_multiple ? selectMultiple : selectOne;
     97 }
     98 
     99 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
    100 {
    101     deselectItemsWithoutValidation(excludeElement);
    102     setNeedsValidityCheck();
    103 }
    104 
    105 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
    106 {
    107     // User interaction such as mousedown events can cause list box select elements to send change events.
    108     // This produces that same behavior for changes triggered by other code running on behalf of the user.
    109     if (!usesMenuList()) {
    110         updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
    111         setNeedsValidityCheck();
    112         if (fireOnChangeNow)
    113             listBoxOnChange();
    114         return;
    115     }
    116 
    117     // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
    118     // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
    119     // The selectOption function does not behave this way, possibly because other callers need a change event even
    120     // in cases where the selected option is not change.
    121     if (optionIndex == selectedIndex())
    122         return;
    123 
    124     selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
    125 }
    126 
    127 bool HTMLSelectElement::hasPlaceholderLabelOption() const
    128 {
    129     // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
    130     //
    131     // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
    132     // Using "size() > 1" here because size() may be 0 in WebKit.
    133     // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
    134     //
    135     // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
    136     // In this case, the display size should be assumed as the default.
    137     // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
    138     //
    139     // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
    140     if (multiple() || size() > 1)
    141         return false;
    142 
    143     int listIndex = optionToListIndex(0);
    144     ASSERT(listIndex >= 0);
    145     if (listIndex < 0)
    146         return false;
    147     return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
    148 }
    149 
    150 String HTMLSelectElement::validationMessage() const
    151 {
    152     if (!willValidate())
    153         return String();
    154     if (customError())
    155         return customValidationMessage();
    156     if (valueMissing())
    157         return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
    158     return String();
    159 }
    160 
    161 bool HTMLSelectElement::valueMissing() const
    162 {
    163     if (!willValidate())
    164         return false;
    165 
    166     if (!isRequired())
    167         return false;
    168 
    169     int firstSelectionIndex = selectedIndex();
    170 
    171     // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
    172     return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
    173 }
    174 
    175 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
    176 {
    177     if (!multiple())
    178         optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
    179     else {
    180         updateSelectedState(listIndex, allowMultiplySelections, shift);
    181         setNeedsValidityCheck();
    182         if (fireOnChangeNow)
    183             listBoxOnChange();
    184     }
    185 }
    186 
    187 bool HTMLSelectElement::usesMenuList() const
    188 {
    189     if (RenderTheme::theme().delegatesMenuListRendering())
    190         return true;
    191 
    192     return !m_multiple && m_size <= 1;
    193 }
    194 
    195 int HTMLSelectElement::activeSelectionStartListIndex() const
    196 {
    197     if (m_activeSelectionAnchorIndex >= 0)
    198         return m_activeSelectionAnchorIndex;
    199     return optionToListIndex(selectedIndex());
    200 }
    201 
    202 int HTMLSelectElement::activeSelectionEndListIndex() const
    203 {
    204     if (m_activeSelectionEndIndex >= 0)
    205         return m_activeSelectionEndIndex;
    206     return lastSelectedListIndex();
    207 }
    208 
    209 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
    210 {
    211     // Make sure the element is ref'd and deref'd so we don't leak it.
    212     RefPtr<HTMLElement> protectNewChild(element);
    213 
    214     if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
    215         return;
    216 
    217     insertBefore(element, before, exceptionState);
    218     setNeedsValidityCheck();
    219 }
    220 
    221 void HTMLSelectElement::remove(int optionIndex)
    222 {
    223     int listIndex = optionToListIndex(optionIndex);
    224     if (listIndex < 0)
    225         return;
    226 
    227     listItems()[listIndex]->remove(IGNORE_EXCEPTION);
    228 }
    229 
    230 void HTMLSelectElement::remove(HTMLOptionElement* option)
    231 {
    232     if (option->ownerSelectElement() != this)
    233         return;
    234 
    235     option->remove(IGNORE_EXCEPTION);
    236 }
    237 
    238 String HTMLSelectElement::value() const
    239 {
    240     const Vector<HTMLElement*>& items = listItems();
    241     for (unsigned i = 0; i < items.size(); i++) {
    242         if (items[i]->hasLocalName(optionTag) && toHTMLOptionElement(items[i])->selected())
    243             return toHTMLOptionElement(items[i])->value();
    244     }
    245     return "";
    246 }
    247 
    248 void HTMLSelectElement::setValue(const String &value)
    249 {
    250     // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
    251     if (value.isNull()) {
    252         setSelectedIndex(-1);
    253         return;
    254     }
    255 
    256     // Find the option with value() matching the given parameter and make it the current selection.
    257     const Vector<HTMLElement*>& items = listItems();
    258     unsigned optionIndex = 0;
    259     for (unsigned i = 0; i < items.size(); i++) {
    260         if (items[i]->hasLocalName(optionTag)) {
    261             if (toHTMLOptionElement(items[i])->value() == value) {
    262                 setSelectedIndex(optionIndex);
    263                 return;
    264             }
    265             optionIndex++;
    266         }
    267     }
    268 
    269     setSelectedIndex(-1);
    270 }
    271 
    272 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
    273 {
    274     if (name == alignAttr) {
    275         // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
    276         // See http://bugs.webkit.org/show_bug.cgi?id=12072
    277         return false;
    278     }
    279 
    280     return HTMLFormControlElementWithState::isPresentationAttribute(name);
    281 }
    282 
    283 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    284 {
    285     if (name == sizeAttr) {
    286         int oldSize = m_size;
    287         // Set the attribute value to a number.
    288         // This is important since the style rules for this attribute can determine the appearance property.
    289         int size = value.toInt();
    290         AtomicString attrSize = AtomicString::number(size);
    291         if (attrSize != value) {
    292             // FIXME: This is horribly factored.
    293             if (Attribute* sizeAttribute = ensureUniqueElementData()->getAttributeItem(sizeAttr))
    294                 sizeAttribute->setValue(attrSize);
    295         }
    296         size = max(size, 1);
    297 
    298         // Ensure that we've determined selectedness of the items at least once prior to changing the size.
    299         if (oldSize != size)
    300             updateListItemSelectedStates();
    301 
    302         m_size = size;
    303         setNeedsValidityCheck();
    304         if (m_size != oldSize && inActiveDocument()) {
    305             lazyReattachIfAttached();
    306             setRecalcListItems();
    307         }
    308     } else if (name == multipleAttr)
    309         parseMultipleAttribute(value);
    310     else if (name == accesskeyAttr) {
    311         // FIXME: ignore for the moment.
    312         //
    313     } else
    314         HTMLFormControlElementWithState::parseAttribute(name, value);
    315 }
    316 
    317 bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
    318 {
    319     return true;
    320 }
    321 
    322 bool HTMLSelectElement::canSelectAll() const
    323 {
    324     return !usesMenuList();
    325 }
    326 
    327 RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
    328 {
    329     if (usesMenuList())
    330         return new RenderMenuList(this);
    331     return new RenderListBox(this);
    332 }
    333 
    334 bool HTMLSelectElement::childShouldCreateRenderer(const Node& child) const
    335 {
    336     if (!HTMLFormControlElementWithState::childShouldCreateRenderer(child))
    337         return false;
    338     if (!usesMenuList())
    339         return child.hasTagName(HTMLNames::optionTag) || isHTMLOptGroupElement(&child);
    340     return false;
    341 }
    342 
    343 PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
    344 {
    345     updateListItemSelectedStates();
    346     return ensureCachedHTMLCollection(SelectedOptions);
    347 }
    348 
    349 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
    350 {
    351     return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get());
    352 }
    353 
    354 void HTMLSelectElement::updateListItemSelectedStates()
    355 {
    356     if (!m_shouldRecalcListItems)
    357         return;
    358     recalcListItems();
    359     setNeedsValidityCheck();
    360 }
    361 
    362 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    363 {
    364     setRecalcListItems();
    365     setNeedsValidityCheck();
    366     m_lastOnChangeSelection.clear();
    367 
    368     HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    369 }
    370 
    371 void HTMLSelectElement::optionElementChildrenChanged()
    372 {
    373     setRecalcListItems();
    374     setNeedsValidityCheck();
    375 
    376     if (renderer()) {
    377         if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
    378             cache->childrenChanged(this);
    379     }
    380 }
    381 
    382 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
    383 {
    384     focus();
    385     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    386 }
    387 
    388 void HTMLSelectElement::setMultiple(bool multiple)
    389 {
    390     bool oldMultiple = this->multiple();
    391     int oldSelectedIndex = selectedIndex();
    392     setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
    393 
    394     // Restore selectedIndex after changing the multiple flag to preserve
    395     // selection as single-line and multi-line has different defaults.
    396     if (oldMultiple != this->multiple())
    397         setSelectedIndex(oldSelectedIndex);
    398 }
    399 
    400 void HTMLSelectElement::setSize(int size)
    401 {
    402     setIntegralAttribute(sizeAttr, size);
    403 }
    404 
    405 Node* HTMLSelectElement::namedItem(const AtomicString& name)
    406 {
    407     return options()->namedItem(name);
    408 }
    409 
    410 Node* HTMLSelectElement::item(unsigned index)
    411 {
    412     return options()->item(index);
    413 }
    414 
    415 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
    416 {
    417     if (index > maxSelectItems - 1)
    418         index = maxSelectItems - 1;
    419     int diff = index - length();
    420     RefPtr<HTMLElement> before = 0;
    421     // Out of array bounds? First insert empty dummies.
    422     if (diff > 0) {
    423         setLength(index, exceptionState);
    424         // Replace an existing entry?
    425     } else if (diff < 0) {
    426         before = toHTMLElement(options()->item(index+1));
    427         remove(index);
    428     }
    429     // Finally add the new element.
    430     if (!exceptionState.hadException()) {
    431         add(option, before.get(), exceptionState);
    432         if (diff >= 0 && option->selected())
    433             optionSelectionStateChanged(option, true);
    434     }
    435 }
    436 
    437 void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
    438 {
    439     if (newLen > maxSelectItems)
    440         newLen = maxSelectItems;
    441     int diff = length() - newLen;
    442 
    443     if (diff < 0) { // Add dummy elements.
    444         do {
    445             RefPtr<Element> option = document().createElement(optionTag, false);
    446             ASSERT(option);
    447             add(toHTMLElement(option), 0, exceptionState);
    448             if (exceptionState.hadException())
    449                 break;
    450         } while (++diff);
    451     } else {
    452         const Vector<HTMLElement*>& items = listItems();
    453 
    454         // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
    455         // of elements that we intend to remove then attempt to remove them one at a time.
    456         Vector<RefPtr<Element> > itemsToRemove;
    457         size_t optionIndex = 0;
    458         for (size_t i = 0; i < items.size(); ++i) {
    459             Element* item = items[i];
    460             if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
    461                 ASSERT(item->parentNode());
    462                 itemsToRemove.append(item);
    463             }
    464         }
    465 
    466         for (size_t i = 0; i < itemsToRemove.size(); ++i) {
    467             Element* item = itemsToRemove[i].get();
    468             if (item->parentNode())
    469                 item->parentNode()->removeChild(item, exceptionState);
    470         }
    471     }
    472     setNeedsValidityCheck();
    473 }
    474 
    475 bool HTMLSelectElement::isRequiredFormControl() const
    476 {
    477     return isRequired();
    478 }
    479 
    480 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
    481 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
    482 // Otherwise, it returns |listIndex|.
    483 // Valid means that it is enabled and an option element.
    484 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
    485 {
    486     ASSERT(direction == -1 || direction == 1);
    487     const Vector<HTMLElement*>& listItems = this->listItems();
    488     int lastGoodIndex = listIndex;
    489     int size = listItems.size();
    490     for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
    491         --skip;
    492         if (!listItems[listIndex]->isDisabledFormControl() && listItems[listIndex]->hasTagName(optionTag)) {
    493             lastGoodIndex = listIndex;
    494             if (skip <= 0)
    495                 break;
    496         }
    497     }
    498     return lastGoodIndex;
    499 }
    500 
    501 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
    502 {
    503     return nextValidIndex(startIndex, SkipForwards, 1);
    504 }
    505 
    506 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
    507 {
    508     if (startIndex == -1)
    509         startIndex = listItems().size();
    510     return nextValidIndex(startIndex, SkipBackwards, 1);
    511 }
    512 
    513 int HTMLSelectElement::firstSelectableListIndex() const
    514 {
    515     const Vector<HTMLElement*>& items = listItems();
    516     int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
    517     if (static_cast<size_t>(index) == items.size())
    518         return -1;
    519     return index;
    520 }
    521 
    522 int HTMLSelectElement::lastSelectableListIndex() const
    523 {
    524     return nextValidIndex(-1, SkipForwards, INT_MAX);
    525 }
    526 
    527 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
    528 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
    529 {
    530     const Vector<HTMLElement*>& items = listItems();
    531     // Can't use m_size because renderer forces a minimum size.
    532     int pageSize = 0;
    533     if (renderer()->isListBox())
    534         pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
    535 
    536     // One page away, but not outside valid bounds.
    537     // If there is a valid option item one page away, the index is chosen.
    538     // If there is no exact one page away valid option, returns startIndex or the most far index.
    539     int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
    540     int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
    541     return nextValidIndex(edgeIndex, direction, skipAmount);
    542 }
    543 
    544 void HTMLSelectElement::selectAll()
    545 {
    546     ASSERT(!usesMenuList());
    547     if (!renderer() || !m_multiple)
    548         return;
    549 
    550     // Save the selection so it can be compared to the new selectAll selection
    551     // when dispatching change events.
    552     saveLastSelection();
    553 
    554     m_activeSelectionState = true;
    555     setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
    556     setActiveSelectionEndIndex(previousSelectableListIndex(-1));
    557 
    558     updateListBoxSelection(false);
    559     listBoxOnChange();
    560     setNeedsValidityCheck();
    561 }
    562 
    563 void HTMLSelectElement::saveLastSelection()
    564 {
    565     if (usesMenuList()) {
    566         m_lastOnChangeIndex = selectedIndex();
    567         return;
    568     }
    569 
    570     m_lastOnChangeSelection.clear();
    571     const Vector<HTMLElement*>& items = listItems();
    572     for (unsigned i = 0; i < items.size(); ++i) {
    573         HTMLElement* element = items[i];
    574         m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
    575     }
    576 }
    577 
    578 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
    579 {
    580     m_activeSelectionAnchorIndex = index;
    581 
    582     // Cache the selection state so we can restore the old selection as the new
    583     // selection pivots around this anchor index.
    584     m_cachedStateForActiveSelection.clear();
    585 
    586     const Vector<HTMLElement*>& items = listItems();
    587     for (unsigned i = 0; i < items.size(); ++i) {
    588         HTMLElement* element = items[i];
    589         m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
    590     }
    591 }
    592 
    593 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
    594 {
    595     m_activeSelectionEndIndex = index;
    596 }
    597 
    598 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
    599 {
    600     ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
    601     ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
    602 
    603     unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
    604     unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
    605 
    606     const Vector<HTMLElement*>& items = listItems();
    607     for (unsigned i = 0; i < items.size(); ++i) {
    608         HTMLElement* element = items[i];
    609         if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
    610             continue;
    611 
    612         if (i >= start && i <= end)
    613             toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
    614         else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
    615             toHTMLOptionElement(element)->setSelectedState(false);
    616         else
    617             toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
    618     }
    619 
    620     scrollToSelection();
    621     setNeedsValidityCheck();
    622     notifyFormStateChanged();
    623 }
    624 
    625 void HTMLSelectElement::listBoxOnChange()
    626 {
    627     ASSERT(!usesMenuList() || m_multiple);
    628 
    629     const Vector<HTMLElement*>& items = listItems();
    630 
    631     // If the cached selection list is empty, or the size has changed, then fire
    632     // dispatchFormControlChangeEvent, and return early.
    633     // FIXME: Why? This looks unreasonable.
    634     if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
    635         dispatchFormControlChangeEvent();
    636         return;
    637     }
    638 
    639     // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
    640     bool fireOnChange = false;
    641     for (unsigned i = 0; i < items.size(); ++i) {
    642         HTMLElement* element = items[i];
    643         bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
    644         if (selected != m_lastOnChangeSelection[i])
    645             fireOnChange = true;
    646         m_lastOnChangeSelection[i] = selected;
    647     }
    648 
    649     if (fireOnChange)
    650         dispatchFormControlChangeEvent();
    651 }
    652 
    653 void HTMLSelectElement::dispatchChangeEventForMenuList()
    654 {
    655     ASSERT(usesMenuList());
    656 
    657     int selected = selectedIndex();
    658     if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
    659         m_lastOnChangeIndex = selected;
    660         m_isProcessingUserDrivenChange = false;
    661         dispatchFormControlChangeEvent();
    662     }
    663 }
    664 
    665 void HTMLSelectElement::scrollToSelection()
    666 {
    667     if (usesMenuList())
    668         return;
    669 
    670     if (RenderObject* renderer = this->renderer())
    671         toRenderListBox(renderer)->selectionChanged();
    672 }
    673 
    674 void HTMLSelectElement::setOptionsChangedOnRenderer()
    675 {
    676     if (RenderObject* renderer = this->renderer()) {
    677         if (usesMenuList())
    678             toRenderMenuList(renderer)->setOptionsChanged(true);
    679         else
    680             toRenderListBox(renderer)->setOptionsChanged(true);
    681     }
    682 }
    683 
    684 const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
    685 {
    686     if (m_shouldRecalcListItems)
    687         recalcListItems();
    688     else {
    689 #if !ASSERT_DISABLED
    690         Vector<HTMLElement*> items = m_listItems;
    691         recalcListItems(false);
    692         ASSERT(items == m_listItems);
    693 #endif
    694     }
    695 
    696     return m_listItems;
    697 }
    698 
    699 void HTMLSelectElement::invalidateSelectedItems()
    700 {
    701     if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
    702         collection->invalidateCache();
    703 }
    704 
    705 void HTMLSelectElement::setRecalcListItems()
    706 {
    707     // FIXME: This function does a bunch of confusing things depending on if it
    708     // is in the document or not.
    709 
    710     m_shouldRecalcListItems = true;
    711     // Manual selection anchor is reset when manipulating the select programmatically.
    712     m_activeSelectionAnchorIndex = -1;
    713     setOptionsChangedOnRenderer();
    714     setNeedsStyleRecalc();
    715     if (!inDocument()) {
    716         if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
    717             collection->invalidateCache();
    718     }
    719     if (!inDocument())
    720         invalidateSelectedItems();
    721 
    722     if (renderer()) {
    723         if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
    724             cache->childrenChanged(this);
    725     }
    726 }
    727 
    728 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
    729 {
    730     m_listItems.clear();
    731 
    732     m_shouldRecalcListItems = false;
    733 
    734     HTMLOptionElement* foundSelected = 0;
    735     HTMLOptionElement* firstOption = 0;
    736     for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
    737         if (!currentElement->isHTMLElement()) {
    738             currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
    739             continue;
    740         }
    741         HTMLElement& current = toHTMLElement(*currentElement);
    742 
    743         // optgroup tags may not nest. However, both FireFox and IE will
    744         // flatten the tree automatically, so we follow suit.
    745         // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
    746         if (isHTMLOptGroupElement(current)) {
    747             m_listItems.append(&current);
    748             if (Element* nextElement = ElementTraversal::firstWithin(current)) {
    749                 currentElement = nextElement;
    750                 continue;
    751             }
    752         }
    753 
    754         if (current.hasTagName(optionTag)) {
    755             m_listItems.append(&current);
    756 
    757             if (updateSelectedStates && !m_multiple) {
    758                 HTMLOptionElement& option = toHTMLOptionElement(current);
    759                 if (!firstOption)
    760                     firstOption = &option;
    761                 if (option.selected()) {
    762                     if (foundSelected)
    763                         foundSelected->setSelectedState(false);
    764                     foundSelected = &option;
    765                 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
    766                     foundSelected = &option;
    767                     foundSelected->setSelectedState(true);
    768                 }
    769             }
    770         }
    771 
    772         if (current.hasTagName(hrTag))
    773             m_listItems.append(&current);
    774 
    775         // In conforming HTML code, only <optgroup> and <option> will be found
    776         // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
    777         // into those tags that we choose to. For web-compat, we should cope
    778         // with the case where odd tags like a <div> have been added but we
    779         // handle this because such tags have already been removed from the
    780         // <select>'s subtree at this point.
    781         currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
    782     }
    783 
    784     if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
    785         firstOption->setSelectedState(true);
    786 }
    787 
    788 int HTMLSelectElement::selectedIndex() const
    789 {
    790     unsigned index = 0;
    791 
    792     // Return the number of the first option selected.
    793     const Vector<HTMLElement*>& items = listItems();
    794     for (size_t i = 0; i < items.size(); ++i) {
    795         HTMLElement* element = items[i];
    796         if (element->hasTagName(optionTag)) {
    797             if (toHTMLOptionElement(element)->selected())
    798                 return index;
    799             ++index;
    800         }
    801     }
    802 
    803     return -1;
    804 }
    805 
    806 void HTMLSelectElement::setSelectedIndex(int index)
    807 {
    808     selectOption(index, DeselectOtherOptions);
    809 }
    810 
    811 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
    812 {
    813     ASSERT(option->ownerSelectElement() == this);
    814     if (optionIsSelected)
    815         selectOption(option->index());
    816     else if (!usesMenuList() || multiple())
    817         selectOption(-1);
    818     else
    819         selectOption(nextSelectableListIndex(-1));
    820 }
    821 
    822 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
    823 {
    824     bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
    825 
    826     const Vector<HTMLElement*>& items = listItems();
    827     int listIndex = optionToListIndex(optionIndex);
    828 
    829     HTMLElement* element = 0;
    830     if (listIndex >= 0) {
    831         element = items[listIndex];
    832         if (element->hasTagName(optionTag)) {
    833             if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
    834                 setActiveSelectionAnchorIndex(listIndex);
    835             if (m_activeSelectionEndIndex < 0 || shouldDeselect)
    836                 setActiveSelectionEndIndex(listIndex);
    837             toHTMLOptionElement(element)->setSelectedState(true);
    838         }
    839     }
    840 
    841     if (shouldDeselect)
    842         deselectItemsWithoutValidation(element);
    843 
    844     // For the menu list case, this is what makes the selected element appear.
    845     if (RenderObject* renderer = this->renderer())
    846         renderer->updateFromElement();
    847 
    848     scrollToSelection();
    849 
    850     if (usesMenuList()) {
    851         m_isProcessingUserDrivenChange = flags & UserDriven;
    852         if (flags & DispatchChangeEvent)
    853             dispatchChangeEventForMenuList();
    854         if (RenderObject* renderer = this->renderer()) {
    855             if (usesMenuList())
    856                 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
    857             else if (renderer->isListBox())
    858                 toRenderListBox(renderer)->selectionChanged();
    859         }
    860     }
    861 
    862     setNeedsValidityCheck();
    863     notifyFormStateChanged();
    864 }
    865 
    866 int HTMLSelectElement::optionToListIndex(int optionIndex) const
    867 {
    868     const Vector<HTMLElement*>& items = listItems();
    869     int listSize = static_cast<int>(items.size());
    870     if (optionIndex < 0 || optionIndex >= listSize)
    871         return -1;
    872 
    873     int optionIndex2 = -1;
    874     for (int listIndex = 0; listIndex < listSize; ++listIndex) {
    875         if (items[listIndex]->hasTagName(optionTag)) {
    876             ++optionIndex2;
    877             if (optionIndex2 == optionIndex)
    878                 return listIndex;
    879         }
    880     }
    881 
    882     return -1;
    883 }
    884 
    885 int HTMLSelectElement::listToOptionIndex(int listIndex) const
    886 {
    887     const Vector<HTMLElement*>& items = listItems();
    888     if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag))
    889         return -1;
    890 
    891     // Actual index of option not counting OPTGROUP entries that may be in list.
    892     int optionIndex = 0;
    893     for (int i = 0; i < listIndex; ++i) {
    894         if (items[i]->hasTagName(optionTag))
    895             ++optionIndex;
    896     }
    897 
    898     return optionIndex;
    899 }
    900 
    901 void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusDirection direction)
    902 {
    903     // Save the selection so it can be compared to the new selection when
    904     // dispatching change events during blur event dispatch.
    905     if (usesMenuList())
    906         saveLastSelection();
    907     HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, direction);
    908 }
    909 
    910 void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
    911 {
    912     // We only need to fire change events here for menu lists, because we fire
    913     // change events for list boxes whenever the selection change is actually made.
    914     // This matches other browsers' behavior.
    915     if (usesMenuList())
    916         dispatchChangeEventForMenuList();
    917     HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
    918 }
    919 
    920 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
    921 {
    922     const Vector<HTMLElement*>& items = listItems();
    923     for (unsigned i = 0; i < items.size(); ++i) {
    924         HTMLElement* element = items[i];
    925         if (element != excludeElement && element->hasTagName(optionTag))
    926             toHTMLOptionElement(element)->setSelectedState(false);
    927     }
    928 }
    929 
    930 FormControlState HTMLSelectElement::saveFormControlState() const
    931 {
    932     const Vector<HTMLElement*>& items = listItems();
    933     size_t length = items.size();
    934     FormControlState state;
    935     for (unsigned i = 0; i < length; ++i) {
    936         if (!items[i]->hasTagName(optionTag))
    937             continue;
    938         HTMLOptionElement* option = toHTMLOptionElement(items[i]);
    939         if (!option->selected())
    940             continue;
    941         state.append(option->value());
    942         if (!multiple())
    943             break;
    944     }
    945     return state;
    946 }
    947 
    948 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
    949 {
    950     const Vector<HTMLElement*>& items = listItems();
    951     size_t loopEndIndex = std::min(items.size(), listIndexEnd);
    952     for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
    953         if (!items[i]->hasLocalName(optionTag))
    954             continue;
    955         if (toHTMLOptionElement(items[i])->value() == value)
    956             return i;
    957     }
    958     return kNotFound;
    959 }
    960 
    961 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
    962 {
    963     recalcListItems();
    964 
    965     const Vector<HTMLElement*>& items = listItems();
    966     size_t itemsSize = items.size();
    967     if (!itemsSize)
    968         return;
    969 
    970     for (size_t i = 0; i < itemsSize; ++i) {
    971         if (!items[i]->hasLocalName(optionTag))
    972             continue;
    973         toHTMLOptionElement(items[i])->setSelectedState(false);
    974     }
    975 
    976     if (!multiple()) {
    977         size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
    978         if (foundIndex != kNotFound)
    979             toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
    980     } else {
    981         size_t startIndex = 0;
    982         for (size_t i = 0; i < state.valueSize(); ++i) {
    983             const String& value = state[i];
    984             size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
    985             if (foundIndex == kNotFound)
    986                 foundIndex = searchOptionsForValue(value, 0, startIndex);
    987             if (foundIndex == kNotFound)
    988                 continue;
    989             toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
    990             startIndex = foundIndex + 1;
    991         }
    992     }
    993 
    994     setOptionsChangedOnRenderer();
    995     setNeedsValidityCheck();
    996 }
    997 
    998 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
    999 {
   1000     bool oldUsesMenuList = usesMenuList();
   1001     m_multiple = !value.isNull();
   1002     setNeedsValidityCheck();
   1003     if (oldUsesMenuList != usesMenuList())
   1004         lazyReattachIfAttached();
   1005 }
   1006 
   1007 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
   1008 {
   1009     const AtomicString& name = this->name();
   1010     if (name.isEmpty())
   1011         return false;
   1012 
   1013     bool successful = false;
   1014     const Vector<HTMLElement*>& items = listItems();
   1015 
   1016     for (unsigned i = 0; i < items.size(); ++i) {
   1017         HTMLElement* element = items[i];
   1018         if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->isDisabledFormControl()) {
   1019             list.appendData(name, toHTMLOptionElement(element)->value());
   1020             successful = true;
   1021         }
   1022     }
   1023 
   1024     // It's possible that this is a menulist with multiple options and nothing
   1025     // will be submitted (!successful). We won't send a unselected non-disabled
   1026     // option as fallback. This behavior matches to other browsers.
   1027     return successful;
   1028 }
   1029 
   1030 void HTMLSelectElement::resetImpl()
   1031 {
   1032     HTMLOptionElement* firstOption = 0;
   1033     HTMLOptionElement* selectedOption = 0;
   1034 
   1035     const Vector<HTMLElement*>& items = listItems();
   1036     for (unsigned i = 0; i < items.size(); ++i) {
   1037         HTMLElement* element = items[i];
   1038         if (!element->hasTagName(optionTag))
   1039             continue;
   1040 
   1041         if (items[i]->fastHasAttribute(selectedAttr)) {
   1042             if (selectedOption && !m_multiple)
   1043                 selectedOption->setSelectedState(false);
   1044             toHTMLOptionElement(element)->setSelectedState(true);
   1045             selectedOption = toHTMLOptionElement(element);
   1046         } else
   1047             toHTMLOptionElement(element)->setSelectedState(false);
   1048 
   1049         if (!firstOption)
   1050             firstOption = toHTMLOptionElement(element);
   1051     }
   1052 
   1053     if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
   1054         firstOption->setSelectedState(true);
   1055 
   1056     setOptionsChangedOnRenderer();
   1057     setNeedsStyleRecalc();
   1058     setNeedsValidityCheck();
   1059 }
   1060 
   1061 #if !OS(WIN)
   1062 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
   1063 {
   1064     if (!RenderTheme::theme().popsMenuByArrowKeys())
   1065         return false;
   1066 
   1067     if (!isSpatialNavigationEnabled(document().frame())) {
   1068         if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
   1069             focus();
   1070             // Calling focus() may cause us to lose our renderer. Return true so
   1071             // that our caller doesn't process the event further, but don't set
   1072             // the event as handled.
   1073             if (!renderer())
   1074                 return true;
   1075 
   1076             // Save the selection so it can be compared to the new selection
   1077             // when dispatching change events during selectOption, which
   1078             // gets called from RenderMenuList::valueChanged, which gets called
   1079             // after the user makes a selection from the menu.
   1080             saveLastSelection();
   1081             if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1082                 menuList->showPopup();
   1083             event->setDefaultHandled();
   1084         }
   1085         return true;
   1086     }
   1087 
   1088     return false;
   1089 }
   1090 #endif
   1091 
   1092 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
   1093 {
   1094     RenderTheme& renderTheme = RenderTheme::theme();
   1095 
   1096     if (event->type() == EventTypeNames::keydown) {
   1097         if (!renderer() || !event->isKeyboardEvent())
   1098             return;
   1099 
   1100         if (platformHandleKeydownEvent(toKeyboardEvent(event)))
   1101             return;
   1102 
   1103         // When using spatial navigation, we want to be able to navigate away
   1104         // from the select element when the user hits any of the arrow keys,
   1105         // instead of changing the selection.
   1106         if (isSpatialNavigationEnabled(document().frame())) {
   1107             if (!m_activeSelectionState)
   1108                 return;
   1109         }
   1110 
   1111         const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
   1112         bool handled = true;
   1113         const Vector<HTMLElement*>& listItems = this->listItems();
   1114         int listIndex = optionToListIndex(selectedIndex());
   1115 
   1116         if (keyIdentifier == "Down" || keyIdentifier == "Right")
   1117             listIndex = nextValidIndex(listIndex, SkipForwards, 1);
   1118         else if (keyIdentifier == "Up" || keyIdentifier == "Left")
   1119             listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
   1120         else if (keyIdentifier == "PageDown")
   1121             listIndex = nextValidIndex(listIndex, SkipForwards, 3);
   1122         else if (keyIdentifier == "PageUp")
   1123             listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
   1124         else if (keyIdentifier == "Home")
   1125             listIndex = nextValidIndex(-1, SkipForwards, 1);
   1126         else if (keyIdentifier == "End")
   1127             listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
   1128         else
   1129             handled = false;
   1130 
   1131         if (handled && static_cast<size_t>(listIndex) < listItems.size())
   1132             selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
   1133 
   1134         if (handled)
   1135             event->setDefaultHandled();
   1136     }
   1137 
   1138     // Use key press event here since sending simulated mouse events
   1139     // on key down blocks the proper sending of the key press event.
   1140     if (event->type() == EventTypeNames::keypress) {
   1141         if (!renderer() || !event->isKeyboardEvent())
   1142             return;
   1143 
   1144         int keyCode = toKeyboardEvent(event)->keyCode();
   1145         bool handled = false;
   1146 
   1147         if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
   1148             // Use space to toggle arrow key handling for selection change or spatial navigation.
   1149             m_activeSelectionState = !m_activeSelectionState;
   1150             event->setDefaultHandled();
   1151             return;
   1152         }
   1153 
   1154         if (renderTheme.popsMenuBySpaceOrReturn()) {
   1155             if (keyCode == ' ' || keyCode == '\r') {
   1156                 focus();
   1157 
   1158                 // Calling focus() may remove the renderer or change the
   1159                 // renderer type.
   1160                 if (!renderer() || !renderer()->isMenuList())
   1161                     return;
   1162 
   1163                 // Save the selection so it can be compared to the new selection
   1164                 // when dispatching change events during selectOption, which
   1165                 // gets called from RenderMenuList::valueChanged, which gets called
   1166                 // after the user makes a selection from the menu.
   1167                 saveLastSelection();
   1168                 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1169                     menuList->showPopup();
   1170                 handled = true;
   1171             }
   1172         } else if (renderTheme.popsMenuByArrowKeys()) {
   1173             if (keyCode == ' ') {
   1174                 focus();
   1175 
   1176                 // Calling focus() may remove the renderer or change the
   1177                 // renderer type.
   1178                 if (!renderer() || !renderer()->isMenuList())
   1179                     return;
   1180 
   1181                 // Save the selection so it can be compared to the new selection
   1182                 // when dispatching change events during selectOption, which
   1183                 // gets called from RenderMenuList::valueChanged, which gets called
   1184                 // after the user makes a selection from the menu.
   1185                 saveLastSelection();
   1186                 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1187                     menuList->showPopup();
   1188                 handled = true;
   1189             } else if (keyCode == '\r') {
   1190                 if (form())
   1191                     form()->submitImplicitly(event, false);
   1192                 dispatchChangeEventForMenuList();
   1193                 handled = true;
   1194             }
   1195         }
   1196 
   1197         if (handled)
   1198             event->setDefaultHandled();
   1199     }
   1200 
   1201     if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
   1202         focus();
   1203         if (renderer() && renderer()->isMenuList()) {
   1204             if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
   1205                 if (menuList->popupIsVisible())
   1206                     menuList->hidePopup();
   1207                 else {
   1208                     // Save the selection so it can be compared to the new
   1209                     // selection when we call onChange during selectOption,
   1210                     // which gets called from RenderMenuList::valueChanged,
   1211                     // which gets called after the user makes a selection from
   1212                     // the menu.
   1213                     saveLastSelection();
   1214                     menuList->showPopup();
   1215                 }
   1216             }
   1217         }
   1218         event->setDefaultHandled();
   1219     }
   1220 
   1221     if (event->type() == EventTypeNames::blur) {
   1222         if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
   1223             if (menuList->popupIsVisible())
   1224                 menuList->hidePopup();
   1225         }
   1226     }
   1227 }
   1228 
   1229 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
   1230 {
   1231     ASSERT(listIndex >= 0);
   1232 
   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();
   1236 
   1237     m_activeSelectionState = true;
   1238 
   1239     bool shiftSelect = m_multiple && shift;
   1240     bool multiSelect = m_multiple && multi && !shift;
   1241 
   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);
   1250     }
   1251 
   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);
   1257 
   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());
   1262 
   1263     // Set the selection state of the clicked option.
   1264     if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->isDisabledFormControl())
   1265         toHTMLOptionElement(clickedElement)->setSelectedState(true);
   1266 
   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
   1270     // clicked.
   1271     if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
   1272         setActiveSelectionAnchorIndex(listIndex);
   1273 
   1274     setActiveSelectionEndIndex(listIndex);
   1275     updateListBoxSelection(!multiSelect);
   1276 }
   1277 
   1278 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
   1279 {
   1280     const Vector<HTMLElement*>& listItems = this->listItems();
   1281 
   1282     if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
   1283         focus();
   1284         // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
   1285         if (!renderer())
   1286             return;
   1287 
   1288         // Convert to coords relative to the list box if needed.
   1289         MouseEvent* mouseEvent = toMouseEvent(event);
   1290         IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
   1291         int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
   1292         if (listIndex >= 0) {
   1293             if (!isDisabledFormControl()) {
   1294 #if OS(MACOSX)
   1295                 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
   1296 #else
   1297                 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
   1298 #endif
   1299             }
   1300             if (Frame* frame = document().frame())
   1301                 frame->eventHandler().setMouseDownMayStartAutoscroll();
   1302 
   1303             event->setDefaultHandled();
   1304         }
   1305     } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
   1306         MouseEvent* mouseEvent = toMouseEvent(event);
   1307         if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
   1308             return;
   1309 
   1310         IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
   1311         int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
   1312         if (listIndex >= 0) {
   1313             if (!isDisabledFormControl()) {
   1314                 if (m_multiple) {
   1315                     // Only extend selection if there is something selected.
   1316                     if (m_activeSelectionAnchorIndex < 0)
   1317                         return;
   1318 
   1319                     setActiveSelectionEndIndex(listIndex);
   1320                     updateListBoxSelection(false);
   1321                 } else {
   1322                     setActiveSelectionAnchorIndex(listIndex);
   1323                     setActiveSelectionEndIndex(listIndex);
   1324                     updateListBoxSelection(true);
   1325                 }
   1326             }
   1327             event->setDefaultHandled();
   1328         }
   1329     } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
   1330         // We didn't start this click/drag on any options.
   1331         if (m_lastOnChangeSelection.isEmpty())
   1332             return;
   1333         // This makes sure we fire dispatchFormControlChangeEvent for a single
   1334         // click. For drag selection, onChange will fire when the autoscroll
   1335         // timer stops.
   1336         listBoxOnChange();
   1337     } else if (event->type() == EventTypeNames::keydown) {
   1338         if (!event->isKeyboardEvent())
   1339             return;
   1340         const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
   1341 
   1342         bool handled = false;
   1343         int endIndex = 0;
   1344         if (m_activeSelectionEndIndex < 0) {
   1345             // Initialize the end index
   1346             if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
   1347                 int startIndex = lastSelectedListIndex();
   1348                 handled = true;
   1349                 if (keyIdentifier == "Down")
   1350                     endIndex = nextSelectableListIndex(startIndex);
   1351                 else
   1352                     endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
   1353             } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
   1354                 int startIndex = optionToListIndex(selectedIndex());
   1355                 handled = true;
   1356                 if (keyIdentifier == "Up")
   1357                     endIndex = previousSelectableListIndex(startIndex);
   1358                 else
   1359                     endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
   1360             }
   1361         } else {
   1362             // Set the end index based on the current end index.
   1363             if (keyIdentifier == "Down") {
   1364                 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
   1365                 handled = true;
   1366             } else if (keyIdentifier == "Up") {
   1367                 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
   1368                 handled = true;
   1369             } else if (keyIdentifier == "PageDown") {
   1370                 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
   1371                 handled = true;
   1372             } else if (keyIdentifier == "PageUp") {
   1373                 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
   1374                 handled = true;
   1375             }
   1376         }
   1377         if (keyIdentifier == "Home") {
   1378             endIndex = firstSelectableListIndex();
   1379             handled = true;
   1380         } else if (keyIdentifier == "End") {
   1381             endIndex = lastSelectableListIndex();
   1382             handled = true;
   1383         }
   1384 
   1385         if (isSpatialNavigationEnabled(document().frame()))
   1386             // Check if the selection moves to the boundary.
   1387             if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
   1388                 return;
   1389 
   1390         if (endIndex >= 0 && handled) {
   1391             // Save the selection so it can be compared to the new selection
   1392             // when dispatching change events immediately after making the new
   1393             // selection.
   1394             saveLastSelection();
   1395 
   1396             ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
   1397             setActiveSelectionEndIndex(endIndex);
   1398 
   1399             bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
   1400             if (selectNewItem)
   1401                 m_activeSelectionState = true;
   1402             // If the anchor is unitialized, or if we're going to deselect all
   1403             // other options, then set the anchor index equal to the end index.
   1404             bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
   1405             if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
   1406                 if (deselectOthers)
   1407                     deselectItemsWithoutValidation();
   1408                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
   1409             }
   1410 
   1411             toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
   1412             if (selectNewItem) {
   1413                 updateListBoxSelection(deselectOthers);
   1414                 listBoxOnChange();
   1415             } else
   1416                 scrollToSelection();
   1417 
   1418             event->setDefaultHandled();
   1419         }
   1420     } else if (event->type() == EventTypeNames::keypress) {
   1421         if (!event->isKeyboardEvent())
   1422             return;
   1423         int keyCode = toKeyboardEvent(event)->keyCode();
   1424 
   1425         if (keyCode == '\r') {
   1426             if (form())
   1427                 form()->submitImplicitly(event, false);
   1428             event->setDefaultHandled();
   1429         } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
   1430             // Use space to toggle selection change.
   1431             m_activeSelectionState = !m_activeSelectionState;
   1432             updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
   1433             listBoxOnChange();
   1434             event->setDefaultHandled();
   1435         }
   1436     }
   1437 }
   1438 
   1439 void HTMLSelectElement::defaultEventHandler(Event* event)
   1440 {
   1441     if (!renderer())
   1442         return;
   1443 
   1444     if (isDisabledFormControl()) {
   1445         HTMLFormControlElementWithState::defaultEventHandler(event);
   1446         return;
   1447     }
   1448 
   1449     if (usesMenuList())
   1450         menuListDefaultEventHandler(event);
   1451     else
   1452         listBoxDefaultEventHandler(event);
   1453     if (event->defaultHandled())
   1454         return;
   1455 
   1456     if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
   1457         KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
   1458         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
   1459             typeAheadFind(keyboardEvent);
   1460             event->setDefaultHandled();
   1461             return;
   1462         }
   1463     }
   1464     HTMLFormControlElementWithState::defaultEventHandler(event);
   1465 }
   1466 
   1467 int HTMLSelectElement::lastSelectedListIndex() const
   1468 {
   1469     const Vector<HTMLElement*>& items = listItems();
   1470     for (size_t i = items.size(); i;) {
   1471         HTMLElement* element = items[--i];
   1472         if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
   1473             return i;
   1474     }
   1475     return -1;
   1476 }
   1477 
   1478 int HTMLSelectElement::indexOfSelectedOption() const
   1479 {
   1480     return optionToListIndex(selectedIndex());
   1481 }
   1482 
   1483 int HTMLSelectElement::optionCount() const
   1484 {
   1485     return listItems().size();
   1486 }
   1487 
   1488 String HTMLSelectElement::optionAtIndex(int index) const
   1489 {
   1490     const Vector<HTMLElement*>& items = listItems();
   1491 
   1492     HTMLElement* element = items[index];
   1493     if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
   1494         return String();
   1495     return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
   1496 }
   1497 
   1498 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
   1499 {
   1500     int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
   1501     if (index < 0)
   1502         return;
   1503     selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
   1504     if (!usesMenuList())
   1505         listBoxOnChange();
   1506 }
   1507 
   1508 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
   1509 {
   1510     // When the element is created during document parsing, it won't have any
   1511     // items yet - but for innerHTML and related methods, this method is called
   1512     // after the whole subtree is constructed.
   1513     recalcListItems();
   1514     HTMLFormControlElementWithState::insertedInto(insertionPoint);
   1515     return InsertionDone;
   1516 }
   1517 
   1518 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
   1519 {
   1520     // First bring into focus the list box.
   1521     if (!focused())
   1522         accessKeyAction(false);
   1523 
   1524     // If this index is already selected, unselect. otherwise update the selected index.
   1525     const Vector<HTMLElement*>& items = listItems();
   1526     int listIndex = optionToListIndex(index);
   1527     if (listIndex >= 0) {
   1528         HTMLElement* element = items[listIndex];
   1529         if (element->hasTagName(optionTag)) {
   1530             if (toHTMLOptionElement(element)->selected())
   1531                 toHTMLOptionElement(element)->setSelectedState(false);
   1532             else
   1533                 selectOption(index, DispatchChangeEvent | UserDriven);
   1534         }
   1535     }
   1536 
   1537     if (usesMenuList())
   1538         dispatchChangeEventForMenuList();
   1539     else
   1540         listBoxOnChange();
   1541 
   1542     scrollToSelection();
   1543 }
   1544 
   1545 unsigned HTMLSelectElement::length() const
   1546 {
   1547     unsigned options = 0;
   1548 
   1549     const Vector<HTMLElement*>& items = listItems();
   1550     for (unsigned i = 0; i < items.size(); ++i) {
   1551         if (items[i]->hasTagName(optionTag))
   1552             ++options;
   1553     }
   1554 
   1555     return options;
   1556 }
   1557 
   1558 void HTMLSelectElement::finishParsingChildren()
   1559 {
   1560     HTMLFormControlElementWithState::finishParsingChildren();
   1561     m_isParsingInProgress = false;
   1562     updateListItemSelectedStates();
   1563 }
   1564 
   1565 bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
   1566 {
   1567     if (!value) {
   1568         exceptionState.throwTypeError(ExceptionMessages::failedToSet(String::number(index), "HTMLSelectElement", "The value provided was not an HTMLOptionElement."));
   1569         return false;
   1570     }
   1571     setOption(index, value.get(), exceptionState);
   1572     return true;
   1573 }
   1574 
   1575 bool HTMLSelectElement::anonymousIndexedSetterRemove(unsigned index, ExceptionState& exceptionState)
   1576 {
   1577     remove(index);
   1578     return true;
   1579 }
   1580 
   1581 bool HTMLSelectElement::isInteractiveContent() const
   1582 {
   1583     return true;
   1584 }
   1585 
   1586 } // namespace
   1587